From 7f54bf32e3c5fab69e5446382d0cc5d98478f632 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 28 Mar 2018 11:32:24 -0400 Subject: [PATCH 001/397] Archive V3 --- .gitignore | 26 +- README.md | 221 +-------------- legacy/{ => V2}/Fix Errors.bat | 0 legacy/{ => V2}/Merge pr without updating.bat | 0 legacy/{ => V2}/OnServerBoot.bat | 0 legacy/{ => V2}/README.md | 0 legacy/{ => V2}/Recompile.bat | 0 legacy/{ => V2}/Reset and Recompile.bat | 0 legacy/{ => V2}/Start Bot.bat | 0 legacy/{ => V2}/Start Server.bat | 78 +++--- legacy/{ => V2}/Update Server.bat | 0 legacy/{ => V2}/Update to PR.bat | 0 legacy/{ => V2}/Update without resetting.bat | 0 legacy/{ => V2}/bin/activepr.bat | 0 legacy/{ => V2}/bin/build.bat | 0 legacy/{ => V2}/bin/copyfromgit.bat | 0 legacy/{ => V2}/bin/findab.bat | 0 legacy/{ => V2}/bin/findbyond.bat | 0 legacy/{ => V2}/bin/findgit.bat | 0 legacy/{ => V2}/bin/getcurdate.bat | 0 legacy/{ => V2}/bin/getunixtime.bat | 0 legacy/{ => V2}/bin/unixtime.vbs | 0 legacy/{ => V2}/bin/updategit.bat | 0 legacy/{ => V2}/config.bat | 0 legacy/{ => V2}/copyexclude.txt | 0 legacy/{ => V2}/disable crash dialog.reg | 8 +- legacy/{ => V2}/install.bat | 0 legacy/V3/.gitignore | 16 ++ .../V3/AssemblyInfo.global.cs | 0 {DMAPI => legacy/V3/DMAPI}/server_tools.dm | 258 +++++++++--------- {DMAPI => legacy/V3/DMAPI}/st_commands.dm | 0 {DMAPI => legacy/V3/DMAPI}/st_interface.dm | 0 legacy/V3/README.md | 240 ++++++++++++++++ .../V3/TGS.CommandLine}/AdminCommands.cs | 0 .../V3/TGS.CommandLine}/App.config | 0 .../V3/TGS.CommandLine}/BYONDCommands.cs | 0 .../V3/TGS.CommandLine}/ChatCommands.cs | 0 .../V3/TGS.CommandLine}/ConfigCommands.cs | 0 .../V3/TGS.CommandLine}/ConsoleCommand.cs | 0 .../V3/TGS.CommandLine}/DDCommands.cs | 0 .../V3/TGS.CommandLine}/DMCommands.cs | 0 .../V3/TGS.CommandLine}/Program.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../V3/TGS.CommandLine}/RepoCommands.cs | 0 .../V3/TGS.CommandLine}/RootCommands.cs | 0 .../V3/TGS.CommandLine}/ServiceCommands.cs | 0 .../TGS.CommandLine}/TGS.CommandLine.csproj | 0 .../V3/TGS.CommandLine}/tgs.ico | Bin .../V3/TGS.ControlPanel}/App.config | 0 .../ControlPanel/ByondPage.cs | 0 .../ControlPanel/ChatPage.cs | 0 .../ControlPanel/ControlPanel.Designer.cs | 0 .../ControlPanel/ControlPanel.cs | 0 .../ControlPanel/ControlPanel.resx | 0 .../ControlPanel/RepoPage.cs | 0 .../ControlPanel/ServerPage.cs | 0 .../ControlPanel/StaticPage.cs | 0 .../V3/TGS.ControlPanel}/CountedForm.cs | 0 .../GitHubLoginPrompt.Designer.cs | 0 .../V3/TGS.ControlPanel}/GitHubLoginPrompt.cs | 0 .../TGS.ControlPanel}/GitHubLoginPrompt.resx | 0 .../InstanceSelector.Designer.cs | 0 .../V3/TGS.ControlPanel}/InstanceSelector.cs | 0 .../TGS.ControlPanel}/InstanceSelector.resx | 0 .../V3/TGS.ControlPanel}/Login.Designer.cs | 0 .../V3/TGS.ControlPanel}/Login.cs | 0 .../V3/TGS.ControlPanel}/Login.resx | 0 .../V3/TGS.ControlPanel}/Program.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../Properties/Settings.Designer.cs | 0 .../Properties/Settings.settings | 0 .../V3/TGS.ControlPanel}/ServerOpForm.cs | 0 .../TGS.ControlPanel}/TGS.ControlPanel.csproj | 0 .../TestMergeManager.Designer.cs | 0 .../V3/TGS.ControlPanel}/TestMergeManager.cs | 0 .../TGS.ControlPanel}/TestMergeManager.resx | 0 .../V3/TGS.ControlPanel}/packages.config | 0 .../V3/TGS.ControlPanel}/tgs.ico | Bin .../V3/TGS.Installer.UI}/App.config | 0 .../V3/TGS.Installer.UI}/FodyWeavers.xml | 0 .../V3/TGS.Installer.UI}/Main.Designer.cs | 0 .../V3/TGS.Installer.UI}/Main.cs | 0 .../V3/TGS.Installer.UI}/Main.resx | 0 .../V3/TGS.Installer.UI}/Program.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../Properties/Resources.Designer.cs | 0 .../Properties/Resources.resx | 0 .../TGS.Installer.UI}/TGS.Installer.UI.csproj | 0 .../V3/TGS.Installer.UI}/app.manifest | 0 .../V3/TGS.Installer.UI}/packages.config | 0 .../V3/TGS.Installer.UI}/tgs.ico | Bin .../V3/TGS.Installer}/Product.wxs | 0 .../V3/TGS.Installer}/TGS.Installer.wixproj | 0 .../V3/TGS.Installer}/packages.config | 0 .../DreamDaemonBridge.cs | 0 .../V3/TGS.Interface.Bridge}/FodyWeavers.xml | 0 .../Properties/AssemblyInfo.cs | 0 .../TGS.Interface.Bridge.csproj | 0 .../V3/TGS.Interface.Bridge}/packages.config | 0 .../AuthenticationHeaderApplicator.cs | 0 .../V3/TGS.Interface}/ChatSetupInfo.cs | 0 .../V3/TGS.Interface}/Client.cs | 0 .../V3/TGS.Interface}/Command.cs | 0 .../Components/Administration.cs | 0 .../V3/TGS.Interface}/Components/Byond.cs | 0 .../V3/TGS.Interface}/Components/Chat.cs | 0 .../V3/TGS.Interface}/Components/Compiler.cs | 0 .../V3/TGS.Interface}/Components/Config.cs | 0 .../TGS.Interface}/Components/Connectivity.cs | 0 .../TGS.Interface}/Components/DreamDaemon.cs | 0 .../V3/TGS.Interface}/Components/Instance.cs | 0 .../Components/InstanceManager.cs | 0 .../V3/TGS.Interface}/Components/Interop.cs | 0 .../V3/TGS.Interface}/Components/Landing.cs | 0 .../TGS.Interface}/Components/Repository.cs | 0 .../V3/TGS.Interface}/Components/Service.cs | 0 .../V3/TGS.Interface}/Definitions.cs | 0 .../V3/TGS.Interface}/Enumerations.cs | 0 .../V3/TGS.Interface}/Helpers.cs | 0 .../V3/TGS.Interface}/IClient.cs | 0 .../V3/TGS.Interface}/IInstance.cs | 0 .../V3/TGS.Interface}/IServer.cs | 0 .../V3/TGS.Interface}/Instance.cs | 0 .../V3/TGS.Interface}/InstanceMetadata.cs | 0 .../TGS.Interface}/Properties/AssemblyInfo.cs | 0 .../V3/TGS.Interface}/PullRequestInfo.cs | 0 .../V3/TGS.Interface}/RemoteLoginInfo.cs | 0 .../V3/TGS.Interface}/RootCommand.cs | 0 .../V3/TGS.Interface}/Server.cs | 0 .../V3/TGS.Interface}/TGS.Interface.csproj | 0 .../V3/TGS.Interface}/TGS.Interface.nuspec | 0 .../V3/TGS.Interface}/packages.config | 0 .../V3/TGS.Interface}/tgs.ico | Bin .../V3/TGS.Server.Console}/App.config | 0 .../V3/TGS.Server.Console}/Console.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../TGS.Server.Console.csproj | 0 .../V3/TGS.Server.Console}/app.manifest | 0 .../V3/TGS.Server.Console}/tgs.ico | Bin .../V3/TGS.Server.Service}/App.config | 0 .../TGS.Server.Service}/ProjectInstaller.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../V3/TGS.Server.Service}/Service.cs | 0 .../TGS.Server.Service.csproj | 0 .../V3/TGS.Server.Service}/tgs.ico | Bin .../V3/TGS.Server}/App.config | 0 .../TGS.Server}/ChatCommands/ByondCommand.cs | 0 .../TGS.Server}/ChatCommands/ChatCommand.cs | 0 .../TGS.Server}/ChatCommands/CommandInfo.cs | 0 .../V3/TGS.Server}/ChatCommands/KekCommand.cs | 0 .../ChatCommands/PullRequestsCommand.cs | 0 .../ChatCommands/RevisionCommand.cs | 0 .../ChatCommands/RootChatCommand.cs | 0 .../ChatCommands/ServerChatCommand.cs | 0 .../ChatCommands/VersionCommand.cs | 0 .../TGS.Server}/ChatProviders/ChatProvider.cs | 0 .../ChatProviders/DiscordChatProvider.cs | 0 .../ChatProviders/IRCChatProvider.cs | 0 .../TGS.Server}/DeprecatedInstanceConfig.cs | 0 .../V3/TGS.Server}/EventID.cs | 0 .../V3/TGS.Server}/Helpers.cs | 0 .../V3/TGS.Server}/ILogger.cs | 0 .../V3/TGS.Server}/Instance/Administration.cs | 0 .../V3/TGS.Server}/Instance/Byond.cs | 0 .../V3/TGS.Server}/Instance/Chat.cs | 0 .../V3/TGS.Server}/Instance/Compiler.cs | 0 .../V3/TGS.Server}/Instance/Config.cs | 0 .../V3/TGS.Server}/Instance/DreamDaemon.cs | 0 .../V3/TGS.Server}/Instance/Instance.cs | 0 .../V3/TGS.Server}/Instance/Interop.cs | 0 .../TGS.Server}/Instance/PreactionHandler.cs | 0 .../V3/TGS.Server}/Instance/Repository.cs | 0 .../V3/TGS.Server}/InstanceConfig.cs | 0 .../V3/TGS.Server}/MessageType.cs | 0 .../V3/TGS.Server}/ProcessExtension.cs | 0 .../V3/TGS.Server}/Properties/AssemblyInfo.cs | 0 .../V3/TGS.Server}/RepoConfig.cs | 0 .../Security/AuthenticationHeaderDecoder.cs | 0 .../Security/RootAuthorizationManager.cs | 0 .../Security/WindowsAuthorizationPolicy.cs | 0 .../V3/TGS.Server}/Server.cs | 0 .../V3/TGS.Server}/ServerConfig.cs | 0 .../V3/TGS.Server}/TGS.Server.csproj | 0 .../V3/TGS.Server}/packages.config | 0 {TGS.Server => legacy/V3/TGS.Server}/tgs.ico | Bin .../Repository/RepositoryCommandTest.cs | 0 .../TestRepoSetPushTestmergeCommitsCommand.cs | 0 .../Repository/TestRepoStatusCommand.cs | 0 .../ControlPanel/TestInstanceSelector.cs | 0 .../V3/TGS.Tests}/OutputProcOverriderTest.cs | 0 .../V3/TGS.Tests}/Properties/AssemblyInfo.cs | 0 .../V3/TGS.Tests}/Server/TestHelpers.cs | 0 .../ServiceInterface/TestHelpers.cs | 0 .../ServiceInterface/TestInterface.cs | 0 .../V3/TGS.Tests}/TGS.Tests.csproj | 0 .../TGS.Tests}/TempDirectoryRequiredTest.cs | 0 .../V3/TGS.Tests}/packages.config | 0 TGS3.json => legacy/V3/TGS3.json | 0 .../V3/TGStationServer3.sln | 0 {Tools => legacy/V3/Tools}/BuildDox.ps1 | 0 {Tools => legacy/V3/Tools}/Config.dm | 0 .../V3/Tools}/CoverageExclusions.runsettings | 0 .../V3/Tools}/DMAPITravisTester.dme | 0 {Tools => legacy/V3/Tools}/Doxyfile | 0 {Tools => legacy/V3/Tools}/PostCIBuild.ps1 | 0 {Tools => legacy/V3/Tools}/SignBasics.ps1 | 0 {Tools => legacy/V3/Tools}/SignMSI.ps1 | 0 {Tools => legacy/V3/Tools}/Test.dm | 0 {Tools => legacy/V3/Tools}/UploadCoverage.ps1 | 0 {Tools => legacy/V3/Tools}/ViewTGS3Logs.xml | 0 {Tools => legacy/V3/Tools}/build_byond.sh | 0 {Tools => legacy/V3/Tools}/install_byond.sh | 0 .../V3/Tools}/tgstation13.org.pfx | Bin tgs.ico => legacy/V3/tgs.ico | Bin 214 files changed, 444 insertions(+), 403 deletions(-) rename legacy/{ => V2}/Fix Errors.bat (100%) rename legacy/{ => V2}/Merge pr without updating.bat (100%) rename legacy/{ => V2}/OnServerBoot.bat (100%) rename legacy/{ => V2}/README.md (100%) rename legacy/{ => V2}/Recompile.bat (100%) rename legacy/{ => V2}/Reset and Recompile.bat (100%) rename legacy/{ => V2}/Start Bot.bat (100%) rename legacy/{ => V2}/Start Server.bat (96%) rename legacy/{ => V2}/Update Server.bat (100%) rename legacy/{ => V2}/Update to PR.bat (100%) rename legacy/{ => V2}/Update without resetting.bat (100%) rename legacy/{ => V2}/bin/activepr.bat (100%) rename legacy/{ => V2}/bin/build.bat (100%) rename legacy/{ => V2}/bin/copyfromgit.bat (100%) rename legacy/{ => V2}/bin/findab.bat (100%) rename legacy/{ => V2}/bin/findbyond.bat (100%) rename legacy/{ => V2}/bin/findgit.bat (100%) rename legacy/{ => V2}/bin/getcurdate.bat (100%) rename legacy/{ => V2}/bin/getunixtime.bat (100%) rename legacy/{ => V2}/bin/unixtime.vbs (100%) rename legacy/{ => V2}/bin/updategit.bat (100%) rename legacy/{ => V2}/config.bat (100%) rename legacy/{ => V2}/copyexclude.txt (100%) rename legacy/{ => V2}/disable crash dialog.reg (97%) rename legacy/{ => V2}/install.bat (100%) create mode 100644 legacy/V3/.gitignore rename AssemblyInfo.global.cs => legacy/V3/AssemblyInfo.global.cs (100%) rename {DMAPI => legacy/V3/DMAPI}/server_tools.dm (97%) rename {DMAPI => legacy/V3/DMAPI}/st_commands.dm (100%) rename {DMAPI => legacy/V3/DMAPI}/st_interface.dm (100%) create mode 100644 legacy/V3/README.md rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/AdminCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/App.config (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/BYONDCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/ChatCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/ConfigCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/ConsoleCommand.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/DDCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/DMCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/Program.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/Properties/AssemblyInfo.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/RepoCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/RootCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/ServiceCommands.cs (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/TGS.CommandLine.csproj (100%) rename {TGS.CommandLine => legacy/V3/TGS.CommandLine}/tgs.ico (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/App.config (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ControlPanel/ByondPage.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ControlPanel/ChatPage.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ControlPanel/ControlPanel.Designer.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ControlPanel/ControlPanel.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ControlPanel/ControlPanel.resx (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ControlPanel/RepoPage.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ControlPanel/ServerPage.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ControlPanel/StaticPage.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/CountedForm.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/GitHubLoginPrompt.Designer.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/GitHubLoginPrompt.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/GitHubLoginPrompt.resx (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/InstanceSelector.Designer.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/InstanceSelector.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/InstanceSelector.resx (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/Login.Designer.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/Login.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/Login.resx (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/Program.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/Properties/AssemblyInfo.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/Properties/Settings.Designer.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/Properties/Settings.settings (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/ServerOpForm.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/TGS.ControlPanel.csproj (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/TestMergeManager.Designer.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/TestMergeManager.cs (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/TestMergeManager.resx (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/packages.config (100%) rename {TGS.ControlPanel => legacy/V3/TGS.ControlPanel}/tgs.ico (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/App.config (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/FodyWeavers.xml (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/Main.Designer.cs (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/Main.cs (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/Main.resx (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/Program.cs (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/Properties/AssemblyInfo.cs (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/Properties/Resources.Designer.cs (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/Properties/Resources.resx (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/TGS.Installer.UI.csproj (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/app.manifest (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/packages.config (100%) rename {TGS.Installer.UI => legacy/V3/TGS.Installer.UI}/tgs.ico (100%) rename {TGS.Installer => legacy/V3/TGS.Installer}/Product.wxs (100%) rename {TGS.Installer => legacy/V3/TGS.Installer}/TGS.Installer.wixproj (100%) rename {TGS.Installer => legacy/V3/TGS.Installer}/packages.config (100%) rename {TGS.Interface.Bridge => legacy/V3/TGS.Interface.Bridge}/DreamDaemonBridge.cs (100%) rename {TGS.Interface.Bridge => legacy/V3/TGS.Interface.Bridge}/FodyWeavers.xml (100%) rename {TGS.Interface.Bridge => legacy/V3/TGS.Interface.Bridge}/Properties/AssemblyInfo.cs (100%) rename {TGS.Interface.Bridge => legacy/V3/TGS.Interface.Bridge}/TGS.Interface.Bridge.csproj (100%) rename {TGS.Interface.Bridge => legacy/V3/TGS.Interface.Bridge}/packages.config (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/AuthenticationHeaderApplicator.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/ChatSetupInfo.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Client.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Command.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Administration.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Byond.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Chat.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Compiler.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Config.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Connectivity.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/DreamDaemon.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Instance.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/InstanceManager.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Interop.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Landing.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Repository.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Components/Service.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Definitions.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Enumerations.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Helpers.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/IClient.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/IInstance.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/IServer.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Instance.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/InstanceMetadata.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Properties/AssemblyInfo.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/PullRequestInfo.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/RemoteLoginInfo.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/RootCommand.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/Server.cs (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/TGS.Interface.csproj (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/TGS.Interface.nuspec (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/packages.config (100%) rename {TGS.Interface => legacy/V3/TGS.Interface}/tgs.ico (100%) rename {TGS.Server.Console => legacy/V3/TGS.Server.Console}/App.config (100%) rename {TGS.Server.Console => legacy/V3/TGS.Server.Console}/Console.cs (100%) rename {TGS.Server.Console => legacy/V3/TGS.Server.Console}/Properties/AssemblyInfo.cs (100%) rename {TGS.Server.Console => legacy/V3/TGS.Server.Console}/TGS.Server.Console.csproj (100%) rename {TGS.Server.Console => legacy/V3/TGS.Server.Console}/app.manifest (100%) rename {TGS.Server.Console => legacy/V3/TGS.Server.Console}/tgs.ico (100%) rename {TGS.Server.Service => legacy/V3/TGS.Server.Service}/App.config (100%) rename {TGS.Server.Service => legacy/V3/TGS.Server.Service}/ProjectInstaller.cs (100%) rename {TGS.Server.Service => legacy/V3/TGS.Server.Service}/Properties/AssemblyInfo.cs (100%) rename {TGS.Server.Service => legacy/V3/TGS.Server.Service}/Service.cs (100%) rename {TGS.Server.Service => legacy/V3/TGS.Server.Service}/TGS.Server.Service.csproj (100%) rename {TGS.Server.Service => legacy/V3/TGS.Server.Service}/tgs.ico (100%) rename {TGS.Server => legacy/V3/TGS.Server}/App.config (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/ByondCommand.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/ChatCommand.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/CommandInfo.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/KekCommand.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/PullRequestsCommand.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/RevisionCommand.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/RootChatCommand.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/ServerChatCommand.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatCommands/VersionCommand.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatProviders/ChatProvider.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatProviders/DiscordChatProvider.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ChatProviders/IRCChatProvider.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/DeprecatedInstanceConfig.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/EventID.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Helpers.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ILogger.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/Administration.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/Byond.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/Chat.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/Compiler.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/Config.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/DreamDaemon.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/Instance.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/Interop.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/PreactionHandler.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Instance/Repository.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/InstanceConfig.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/MessageType.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ProcessExtension.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Properties/AssemblyInfo.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/RepoConfig.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Security/AuthenticationHeaderDecoder.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Security/RootAuthorizationManager.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Security/WindowsAuthorizationPolicy.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/Server.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/ServerConfig.cs (100%) rename {TGS.Server => legacy/V3/TGS.Server}/TGS.Server.csproj (100%) rename {TGS.Server => legacy/V3/TGS.Server}/packages.config (100%) rename {TGS.Server => legacy/V3/TGS.Server}/tgs.ico (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/CommandLine/Commands/Repository/RepositoryCommandTest.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/CommandLine/Commands/Repository/TestRepoStatusCommand.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/ControlPanel/TestInstanceSelector.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/OutputProcOverriderTest.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/Properties/AssemblyInfo.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/Server/TestHelpers.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/ServiceInterface/TestHelpers.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/ServiceInterface/TestInterface.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/TGS.Tests.csproj (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/TempDirectoryRequiredTest.cs (100%) rename {TGS.Tests => legacy/V3/TGS.Tests}/packages.config (100%) rename TGS3.json => legacy/V3/TGS3.json (100%) rename TGStationServer3.sln => legacy/V3/TGStationServer3.sln (100%) rename {Tools => legacy/V3/Tools}/BuildDox.ps1 (100%) rename {Tools => legacy/V3/Tools}/Config.dm (100%) rename {Tools => legacy/V3/Tools}/CoverageExclusions.runsettings (100%) rename {Tools => legacy/V3/Tools}/DMAPITravisTester.dme (100%) rename {Tools => legacy/V3/Tools}/Doxyfile (100%) rename {Tools => legacy/V3/Tools}/PostCIBuild.ps1 (100%) rename {Tools => legacy/V3/Tools}/SignBasics.ps1 (100%) rename {Tools => legacy/V3/Tools}/SignMSI.ps1 (100%) rename {Tools => legacy/V3/Tools}/Test.dm (100%) rename {Tools => legacy/V3/Tools}/UploadCoverage.ps1 (100%) rename {Tools => legacy/V3/Tools}/ViewTGS3Logs.xml (100%) rename {Tools => legacy/V3/Tools}/build_byond.sh (100%) mode change 100755 => 100644 rename {Tools => legacy/V3/Tools}/install_byond.sh (100%) mode change 100755 => 100644 rename {Tools => legacy/V3/Tools}/tgstation13.org.pfx (100%) rename tgs.ico => legacy/V3/tgs.ico (100%) diff --git a/.gitignore b/.gitignore index 90b3b65a9c..bc48bd8314 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,12 @@ -#Visual studio stuff -.vscode/* -.vs/* - -#VS stuff - -*/bin/* -*/obj/* -*/Debug/* -*/Release/* - -packages/* +[Oo]bj/ +[Bb]in/ +.nuget/ +_ReSharper.* +packages/ +artifacts/ +.vs/ *.user -*.dmb -*.int -/TestResults +*.suo +*.userprefs +*DS_Store +*.sln.ide \ No newline at end of file diff --git a/README.md b/README.md index fd2e6d2d93..85a09ea251 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Tgstation Toolkit: +# tgstation-server v4: [![Build status](https://ci.appveyor.com/api/projects/status/7t1h7bvuha0p9j5f/branch/master?svg=true)](https://ci.appveyor.com/project/Cyberboss/tgstation-server-tools/branch/master) [![Build Status](https://travis-ci.org/tgstation/tgstation-server.svg?branch=master)](https://travis-ci.org/tgstation/tgstation-server) [![codecov](https://codecov.io/gh/tgstation/tgstation-server/branch/master/graph/badge.svg)](https://codecov.io/gh/tgstation/tgstation-server) [![Waffle.io - Columns and their card count](https://badge.waffle.io/tgstation/tgstation-server.png?columns=all)](https://waffle.io/tgstation/tgstation-server?utm_source=badge) @@ -9,223 +9,12 @@ [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg)](http://forthebadge.com) -This is a toolset to manage a production server of /tg/Station13 (and its forks). It includes the ability to update the server without having to stop or shutdown the server (the update will take effect next round) the ability start the server and restart it if it crashes, as well as systems for fixing errors and merging GitHub Pull Requests locally. +This is a toolset to manage a production BYOND server. It includes the ability to update the server without having to stop or shutdown the server (the update will take effect on a "reboot" of the server) the ability start the server and restart it if it crashes, as well as systems for fixing errors and merging GitHub Pull Requests locally. -Generally, updates force a live tracking of the configured git repo, resetting local modifications. If you plan to make modifications, set up a new git repo to store your version of the code in, and point this script to that in the config (explained below). This can be on github or a local repo using file:/// urls. +Generally, updates force a live tracking of the configured git repo, resetting local modifications. If you plan to make modifications, set up a new git repo to store your version of the code in, and point this script to that in the config (explained below). This can be on GitHub or a local repo using file:/// urls. -Requires python 2.7/3.6 to be installed for changelog generation - -### Legacy Server -* The old cmd script server can be found in the legacy tree - - -## Installing -1. Either compile from source (requires .NET Framework 4.5.2, Nuget, and WiX toolset 4.0) or download the latest [release from github](https://github.com/tgstation/tgstation-server-tools/releases) -1. Unzip setup files -1. Run the installer or use the console server - -## Installing (GUI): -1. Launch `TGControlPanel.exe` as an administrator. A shortcut can be found on your desktop -1. Use the `Create Instance` button to create a new server instance -1. Go to the `Repository` Tab and set the remote address and branch of the git you with to track -1. Hit the clone button -1. While waiting go to the `BYOND` tab and install the BYOND version you wish -1. You may also configure an IRC and/or discord bot for the server on the chat tab -1. Once the clone is complete you may set up a committer identity, user name, and password on the `Repository` tab for pushing changelog updates -1. Go to the `Server` tab and click the `Initialize Game Folders` button -1. Optionally change the `Project Path` Setting from tgstation to wherever the dme/dmb pair are in your repository -1. Optionally tick the Autostart box if you wish to have your server start with Windows -1. When game folder initialization is complete, click the `Copy from Repo and Compile` option - -## Installing (CL example): -This process is identical to the above steps in command line mode. You can always learn more about a command using `?` i.e. `repo ?` -1. Launch TGCommandLine.exe as an administrator (running with no parameters puts you in interactive mode) -1. `service set-python-path C:\Python27` -1. `service create-instance "TGS" D:\tgstation` -1. `instance` And enter `TGS`. If you aren't using interactive mode, the following commands must be suffixed with `--instance TGS` -1. `repo setup https://github.com/tgstation/tgstation master` -1. `byond update 511.1385` -1. `irc nick TGS3Test` -1. `irc set-auth-mode channel-mode` -1. `irc set-auth-level %` -1. `irc setup-auth NickServ "id hunter2"` Yes this is the real password, please use it only for testing -1. `irc join botbus dev` -1. `irc join botbus admin` -1. `irc join botbus wd` -1. `irc join botbus game` -1. `irc enable` -1. `discord set-token Rjfa93jlksjfj934jlkjasf8a08wfl.asdjfj08e44` See https://discordapp.com/developers/docs/topics/oauth2#bots -1. `discord set-auth-mode role-id` -1. `discord addmin 192837419273409` See how to get a role id: https://www.reddit.com/r/discordapp/comments/5bezg2/role_id/. Note that if you `discord set-auth-mode user-id` you'll need to use user ids (Enable developer mode, right click user, `Copy ID`) -1. `discord join 12341234453235 dev` This is a channel id (Enable developer mode, right click channel, `Copy ID`) -1. `discord join 34563456344245 dev` -1. `discord join 23452362574456 admin` -1. `discord join 23452362574456 wd` -1. `discord join 53457345736788 game` -1. `discord enable` -1. `repo set-name tgstation-server` These two lines specify the changelog's committer identity. They are not mirrored in the GUI -1. `repo set-email tgstation-server@tgstation13.org` -1. `repo set-credentials` And follow the prompts -1. `dm project-name tgstation` -1. `dd autostart on` -1. `repo status` To check the clone job status -1. `dm initialize --wait` -1. `dd start` - -## Setting up Multi-User - -1. Create windows accounts for those you wish to have access to the service -1. Join them in a common windows group -1. Run TGCommandLine.exe as an administrator -1. `admin set-group --instance ""` - -Note: Due to internal functionality, a user who has access to at least one server instance will be able to view the metadata (Name, path, Logging ID, and Enabled status) of all instances. - -## Setting up Remote access - -1. Obtain an SSL certificate to secure the connection (this is beyond the scope of this guide) -1. Either stick with the default port `38607` or change it with `admin set-port ` -1. [Bind the SSL certificate to the port](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate) - - e.g. `netsh http add sslcert ipport=0.0.0.0: certhash= appid={F32EDA25-0855-411C-AF5E-F0D042917E2D}` - - The `appid` GUID actually doesn't matter, but for sanity, you should use the GUID of TGServerService.exe as printed above - - Power shell users remember to quit the appid: `netsh http add sslcert ipport=0.0.0.0: certhash= appid="{F32EDA25-0855-411C-AF5E-F0D042917E2D}"` as {} has special meaning in powershell -1. Ensure the port can be acccessed from the internet -1. Log in from any computer using a username and password from the service computer in either the CLI or GUI - -## Updating - -The service supports updates while running a DreamDaemon instance. Simply install an updated version as you normally would and process ownership will transfer smoothly to the new service. - -### Folders and Files (None of these should be touched): -* `Game//` - * This will house two copies of the game code, one for updating and one for live. When updating, it will automatically swap them. - -* `Static/` - * This contains the `data/` and `config/` folders from the code. They are stored here and a symbolic link is created in the `Game` folders pointing to here. - * Resetting the repository will create a backup of this and reinitalize it - -* `Game/Live/` - * This is a symbolic link pointing to current "live" folder. - * When the server is updated, we just point this to the updating folder so that the update takes place next round. - -* `Diagnostics` - * This contains various timestamped diagnostic information for DreamDaemon invocations - -* `Repository/` - * This contains the actual git repository, all changes in here will be overwritten during update operations. - -* `RepoKey/` - * This contains ssh key information for automatic changelog pushing. - -* `EventHandlers/` - * This contains batch files that run after certain events, currently only precompile and postcompile events are implemented. If you'd like an event handler you should create a file in this folder with the following name: `{event_name}.bat` for example: if I want an event handler for precompile, I would name it `precompile.bat`. - -* `BYOND/` - * This contains the actual BYOND installation the server uses - -* `BYOND_staging/` - * This appears when a BYOND update is queued but can't currently be applied due to usage of the current BYOND version. It will be applied at the first possible moment. Restarting the service deletes this folder - -* `BYOND_revision.zip` - * This is a queued update downloaded from BYOND, it will be unzipped into BYOND_staging and deleted. Restarting the service deletes this file - -* `TGDreamDaemonBridge.dll` - * This is the .dll the TGS3 API `call()()`s into to RPC the server instance that runs it - * Instance -> DreamDaemon communication is achieved via `world/Topic()` - -* `prtestjob.json` - * This contains information about current test merged pull requests in the Repository folder - -* `TGS3.json` - * This is a copy of `TGS3.json` from the Repository. If a repostory change creates differences between the two, update operations will be blocked until the user confirms they want to change it - -* `Instance.json` - * The internal configuration settings for a server instance. Note that access to this file bypasses API user restrictions - -### Codebase integration -To get the TGS3 API for your code base, import the 3 .dm files in the `DMAPI` folder into your include structure, then fill out the configuration as documented in the comments of server_tools.dm. Then, you may want to add a `TGS3.json` file to specify any static directories and .dlls your codebase uses, along with the optional changelog compile options. Any changes to `TGS3.json` will block compiler operations until a server operator manually approves them. - -### Starting the game server: -To run the game server, open the `Server` tab of the control panel and click either `Start` - -It will restart the game server if it shutdowns for any reason, giving up after 5 tries in under a minute - -### Updating the server: -To update the server, open the `Server` tab of the control panel. Click `Update Server`. (it will git pull, compile, all that jazz). Note that this button won't clear test merges. - -(Note: Updating automatically does a code reset, clearing ALL changes to the local git repo, including manual changes (This will not change any data in the `Static/` folder)) - -Updates do not require the server to be shutdown, changes will apply next round if the server is currently running. - -All DM compilation will log to the server what commit they happened at and create a backup tag in the local repository. - -### Locally merge GitHub Pull Requests (PR test merge): -This feature currently only works if github is the remote (git server). - -Running these will merge the pull request then recompile the server, changes take effect the next round if the server is currently running. - -There are two flavors to this. -* The manual method is to use the repo page's merge PR button for fine grain control over PR merging. Each time this button is pressed, the latest commit of the specified PR# will be merged into the repo -* The managed method is the Server page's `Test Merge Manager` which uses the GitHub API to get information about available PRs. - * Open PRs are listed by default - * Currently test merged PRs are checked off and listed at the top when it's opened -* If a PR contains a label that contains the text `test` (case-insensitive) it will be listed on top and marked as `TESTING REQUESTED` - * If a PR has been updated since it was test merged, two entries for it will appear. One listed as `OUTDATED` and specifying the commit it was merged at - * You can change the initial update action of the server with the radio buttons in the bottom left. `Update to Remote` fully resets and updates the server before merging PRs. `Update To Origin` does the same based off the local repository's `origin` remote. `No Update` will merge the PRs without any prior action - * Checking off PRs here and then hitting apply will run the selected update action, optionally generate and push a changelog (see below), merge the PRs, and then compile. `Update Server` will keep any active merged PRs until they are manually removed or merged on the `origin` remote. - * Given that the control panel uses GitHub's API to populate the `Test Merge Manager` you may be prompted for your credentials if you make too many requests. This will create a personal access token with public access on your account specifying its use to bypass the GitHub API rate limit. - -You can clear all active test merges using `Reset to Origin Branch` in the `Repository` tab and the using `Copy from Repo and Compile` in the server tab (explained below). You can also use the `Reset All Test Merges` button on the server tab for a concise solution. - -### The Compiler -* `Server` -> `Copy from Repo and Compile` - * Copies the local repository code, compiles it, and stages it to apply next round -* `Server` -> `Initialize Game Folders` - * Requires the server not be running, rebuilds the `Game` folder and then does `Copy from Repo and Compile` - * Required on first setup - -### Starting everything when the computer/server boots -Just tick the `Autostart` option in the `Server` tab or run `dd autostart on` on the command line. As it's a windows service, it will automatically run without having to log ing - -### Enabling the BYOND Webclient -Just tick the `Webclient` option in the `Server` tab or run `dd webclient on` on the command line. - -### Static Configuration -* The `Static Files` page of the control panel lets you modify all files in directories you specified in `TGS3.json` -* The files are modified here using the Windows credentials of the active user, feel free to manually set ACLs on them -* You may optionally rebuild the entire `Static` folder from the repository using the `Recreate Static Directory` button. This will copy the original files from the repository based on the current `TGS3.json`. This requires DreamDaemon not be running - -### Modifying Your Code - -Any `.dm` files included in the root level of the `Static` directory are automatically copied over and included before anything else in your `.dme` for compilation. Use this to configure compile options as you see fit. - -### Moving, Renaming, and Detaching instances - -* Instances can be renamed, but this requires a temporary offlining of them (this includes interface access, DreamDaemon, and chat bots) -* Detaching an instance simply removes it from the main server's configuration and control, leaving it free for the user to manipulate -* Importing an instance will work as long as - 1. No other instance currently in the server has the same name - 1. It is not the same path as another instance - Note that importing an instance as a different windows user (this is always different across machines) will result in a loss of chat configuration due to the encryption scheme - -### Viewing Server Logs -* Service logs are stored in the Windows event viewer under `Windows Logs` -> `Application`. You'll need to filter this list for `TG Station Server` -* Every event type is keyed with an ID. A complete listing of these IDs and their purpose can be found [here](https://github.com/tgstation/tgstation-server/blob/master/TGS.Server/EventID.cs). Event IDs from different instances are offset by the logging ID the instance was assigned when it was started. -* You can also import the custom view `View TGS3 Logs.xml` in this folder to have them automatically filtered -* Servers running in console mode only use std_out - -### Enabling upstream changelog generation -* The repository will automatically create an ssh version of the initial origin remote and can optionally push generated changelogs to your git through it -* The repository can only authenticate using ssh public key authentication -* To enable this feature, simply create `public_key.txt` and `private_key.txt` in a folder called RepoKey in the server directory -* The private key must be in `-----BEGIN RSA PRIVATE KEY-----` format and the public key must be in `ssh-rsa` format. You can generate a keypair like this using the converter in the dropdown menu of PuTTYGen. See github guidelines for setting this up here: https://help.github.com/articles/connecting-to-github-with-ssh/ -* The server will be able to read these files regardless of their permissions, so the responsibility is on you to set their ACL's so they can't be read by those that shouldn't - -### Synchronized test merge commits -* An instance can push branchless test merge commits to GitHub if the `RepoKey` folder is setup -* This pushes all test merge commits to the remote branch `___TGS3TempBranch` and then deletes it -* This provides public reference information about the test merge commit and time since it will appear in the PR in questing -* To enable this, check the `Sync Commits` button on the `Repository` page of the control panel or run `repo push-testmerges on --instance ""` from TGCommandLine +### Legacy Servers +* Versions 3 and 4 can be found in the `legacy/` directory ## CONTRIBUTING diff --git a/legacy/Fix Errors.bat b/legacy/V2/Fix Errors.bat similarity index 100% rename from legacy/Fix Errors.bat rename to legacy/V2/Fix Errors.bat diff --git a/legacy/Merge pr without updating.bat b/legacy/V2/Merge pr without updating.bat similarity index 100% rename from legacy/Merge pr without updating.bat rename to legacy/V2/Merge pr without updating.bat diff --git a/legacy/OnServerBoot.bat b/legacy/V2/OnServerBoot.bat similarity index 100% rename from legacy/OnServerBoot.bat rename to legacy/V2/OnServerBoot.bat diff --git a/legacy/README.md b/legacy/V2/README.md similarity index 100% rename from legacy/README.md rename to legacy/V2/README.md diff --git a/legacy/Recompile.bat b/legacy/V2/Recompile.bat similarity index 100% rename from legacy/Recompile.bat rename to legacy/V2/Recompile.bat diff --git a/legacy/Reset and Recompile.bat b/legacy/V2/Reset and Recompile.bat similarity index 100% rename from legacy/Reset and Recompile.bat rename to legacy/V2/Reset and Recompile.bat diff --git a/legacy/Start Bot.bat b/legacy/V2/Start Bot.bat similarity index 100% rename from legacy/Start Bot.bat rename to legacy/V2/Start Bot.bat diff --git a/legacy/Start Server.bat b/legacy/V2/Start Server.bat similarity index 96% rename from legacy/Start Server.bat rename to legacy/V2/Start Server.bat index b926db5c61..0312a22a5c 100644 --- a/legacy/Start Server.bat +++ b/legacy/V2/Start Server.bat @@ -1,39 +1,39 @@ -@echo off -@title SERVER WATCHDOG -call config.bat -call bin\findbyond.bat - -echo Welcome to the start server watch dog script, This will start the server and make sure it stays running. To continue, press any key or wait 30 seconds. -timeout 30 - -if not exist gamedata\data\logs\runtimes mkdir gamedata\data\logs\runtimes\ - -@call python bot\nudge.py "WATCHDOG" "Watch Dog online. Starting server" >nul 2>nul -:START - -call bin\getcurdate.bat - -call bin\getunixtime.bat UNIXTIME - -echo %UNIXTIME% - -set STARTTIME=%UNIXTIME% - -cls -echo Watch Dog. -echo Server Running. Watching for server exits. -start /WAIT /ABOVENORMAL "" dreamdaemon.exe gamefolder\%PROJECTNAME%.dmb -port %PORT% -trusted -close -public -verbose -cls - -call bin\getunixtime.bat UNIXTIME - -SET /A Result=%UNIXTIME% - %STARTTIME% -SET /A Result=180 - (%Result%/3) -if %Result% LSS 0 set /A Result=0 - -echo Watch Dog. -echo Server exit detected. Restarting in %Result% seconds. -@python bot\nudge.py "WATCHDOG" "Server exit detected. Restarting server in %Result% seconds." >nul 2>nul -timeout %Result% - -goto :START +@echo off +@title SERVER WATCHDOG +call config.bat +call bin\findbyond.bat + +echo Welcome to the start server watch dog script, This will start the server and make sure it stays running. To continue, press any key or wait 30 seconds. +timeout 30 + +if not exist gamedata\data\logs\runtimes mkdir gamedata\data\logs\runtimes\ + +@call python bot\nudge.py "WATCHDOG" "Watch Dog online. Starting server" >nul 2>nul +:START + +call bin\getcurdate.bat + +call bin\getunixtime.bat UNIXTIME + +echo %UNIXTIME% + +set STARTTIME=%UNIXTIME% + +cls +echo Watch Dog. +echo Server Running. Watching for server exits. +start /WAIT /ABOVENORMAL "" dreamdaemon.exe gamefolder\%PROJECTNAME%.dmb -port %PORT% -trusted -close -public -verbose +cls + +call bin\getunixtime.bat UNIXTIME + +SET /A Result=%UNIXTIME% - %STARTTIME% +SET /A Result=180 - (%Result%/3) +if %Result% LSS 0 set /A Result=0 + +echo Watch Dog. +echo Server exit detected. Restarting in %Result% seconds. +@python bot\nudge.py "WATCHDOG" "Server exit detected. Restarting server in %Result% seconds." >nul 2>nul +timeout %Result% + +goto :START diff --git a/legacy/Update Server.bat b/legacy/V2/Update Server.bat similarity index 100% rename from legacy/Update Server.bat rename to legacy/V2/Update Server.bat diff --git a/legacy/Update to PR.bat b/legacy/V2/Update to PR.bat similarity index 100% rename from legacy/Update to PR.bat rename to legacy/V2/Update to PR.bat diff --git a/legacy/Update without resetting.bat b/legacy/V2/Update without resetting.bat similarity index 100% rename from legacy/Update without resetting.bat rename to legacy/V2/Update without resetting.bat diff --git a/legacy/bin/activepr.bat b/legacy/V2/bin/activepr.bat similarity index 100% rename from legacy/bin/activepr.bat rename to legacy/V2/bin/activepr.bat diff --git a/legacy/bin/build.bat b/legacy/V2/bin/build.bat similarity index 100% rename from legacy/bin/build.bat rename to legacy/V2/bin/build.bat diff --git a/legacy/bin/copyfromgit.bat b/legacy/V2/bin/copyfromgit.bat similarity index 100% rename from legacy/bin/copyfromgit.bat rename to legacy/V2/bin/copyfromgit.bat diff --git a/legacy/bin/findab.bat b/legacy/V2/bin/findab.bat similarity index 100% rename from legacy/bin/findab.bat rename to legacy/V2/bin/findab.bat diff --git a/legacy/bin/findbyond.bat b/legacy/V2/bin/findbyond.bat similarity index 100% rename from legacy/bin/findbyond.bat rename to legacy/V2/bin/findbyond.bat diff --git a/legacy/bin/findgit.bat b/legacy/V2/bin/findgit.bat similarity index 100% rename from legacy/bin/findgit.bat rename to legacy/V2/bin/findgit.bat diff --git a/legacy/bin/getcurdate.bat b/legacy/V2/bin/getcurdate.bat similarity index 100% rename from legacy/bin/getcurdate.bat rename to legacy/V2/bin/getcurdate.bat diff --git a/legacy/bin/getunixtime.bat b/legacy/V2/bin/getunixtime.bat similarity index 100% rename from legacy/bin/getunixtime.bat rename to legacy/V2/bin/getunixtime.bat diff --git a/legacy/bin/unixtime.vbs b/legacy/V2/bin/unixtime.vbs similarity index 100% rename from legacy/bin/unixtime.vbs rename to legacy/V2/bin/unixtime.vbs diff --git a/legacy/bin/updategit.bat b/legacy/V2/bin/updategit.bat similarity index 100% rename from legacy/bin/updategit.bat rename to legacy/V2/bin/updategit.bat diff --git a/legacy/config.bat b/legacy/V2/config.bat similarity index 100% rename from legacy/config.bat rename to legacy/V2/config.bat diff --git a/legacy/copyexclude.txt b/legacy/V2/copyexclude.txt similarity index 100% rename from legacy/copyexclude.txt rename to legacy/V2/copyexclude.txt diff --git a/legacy/disable crash dialog.reg b/legacy/V2/disable crash dialog.reg similarity index 97% rename from legacy/disable crash dialog.reg rename to legacy/V2/disable crash dialog.reg index 07e792d874..d1c477a445 100644 --- a/legacy/disable crash dialog.reg +++ b/legacy/V2/disable crash dialog.reg @@ -1,4 +1,4 @@ -Windows Registry Editor Version 5.00 - -[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting] -"DontShowUI"=dword:00000001 +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting] +"DontShowUI"=dword:00000001 diff --git a/legacy/install.bat b/legacy/V2/install.bat similarity index 100% rename from legacy/install.bat rename to legacy/V2/install.bat diff --git a/legacy/V3/.gitignore b/legacy/V3/.gitignore new file mode 100644 index 0000000000..90b3b65a9c --- /dev/null +++ b/legacy/V3/.gitignore @@ -0,0 +1,16 @@ +#Visual studio stuff +.vscode/* +.vs/* + +#VS stuff + +*/bin/* +*/obj/* +*/Debug/* +*/Release/* + +packages/* +*.user +*.dmb +*.int +/TestResults diff --git a/AssemblyInfo.global.cs b/legacy/V3/AssemblyInfo.global.cs similarity index 100% rename from AssemblyInfo.global.cs rename to legacy/V3/AssemblyInfo.global.cs diff --git a/DMAPI/server_tools.dm b/legacy/V3/DMAPI/server_tools.dm similarity index 97% rename from DMAPI/server_tools.dm rename to legacy/V3/DMAPI/server_tools.dm index 2540dac4ba..444d6be8b4 100644 --- a/DMAPI/server_tools.dm +++ b/legacy/V3/DMAPI/server_tools.dm @@ -1,129 +1,129 @@ -// /tg/station 13 server tools API -#define SERVICE_API_VERSION_STRING "3.2.0.3" - -//CONFIGURATION -//use this define if you want to do configuration outside of this file -#ifndef SERVER_TOOLS_EXTERNAL_CONFIGURATION -//Comment this out once you've filled in the below -#error /tg/station server tools interface unconfigured - -//Required interfaces (fill in with your codebase equivalent): - -//create a global variable named `Name` and set it to `Value` -//These globals must not be modifiable from anywhere outside of the server tools -#define SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(Name, Value) -//Read the value in the global variable `Name` -#define SERVER_TOOLS_READ_GLOBAL(Name) -//Set the value in the global variable `Name` to `Value` -#define SERVER_TOOLS_WRITE_GLOBAL(Name, Value) -//display an announcement `message` from the server to all players -#define SERVER_TOOLS_WORLD_ANNOUNCE(message) -//Write a string `message` to a server log -#define SERVER_TOOLS_LOG(message) -//Notify current in-game administrators of a string `event` -#define SERVER_TOOLS_NOTIFY_ADMINS(event) -//The current amount of connected clients -#define SERVER_TOOLS_CLIENT_COUNT -#endif - -//Required hooks: - -//Put this somewhere in /world/New() that is always run -#define SERVER_TOOLS_ON_NEW ServiceInit() -//Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified -#define SERVER_TOOLS_ON_TOPIC var/service_topic_return = ServiceCommand(params2list(T)); if(service_topic_return) return service_topic_return -//Put at the beginning of world/Reboot(reason) -#define SERVER_TOOLS_ON_REBOOT ServiceReboot() - -//Optional callable functions: - -//Returns the string version of the API -#define SERVER_TOOLS_API_VERSION ServiceAPIVersion() -//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise -//No function below this succeed if this is FALSE -#define SERVER_TOOLS_PRESENT RunningService() -//Gets the current version of the service running the server -#define SERVER_TOOLS_VERSION ServiceVersion() -//Forces a hard reboot of BYOND by ending the process -//unlike del(world) clients will try to reconnect -//If the service has not requested a shutdown, the world will reboot shortly after -#define SERVER_TOOLS_REBOOT_BYOND(silent) world.ServiceEndProcess(silent) -/* - Gets the list of any testmerged github pull requests - - "[PR Number]" => list( - "title" -> PR title - "commit" -> Full hash of commit merged - "author" -> Github username of the author of the PR - ) -*/ -#define SERVER_TOOLS_PR_LIST GetTestMerges() -//Sends a message to connected game chats -#define SERVER_TOOLS_CHAT_BROADCAST(message) world.ChatBroadcast(message) -//Sends a message to connected admin chats -#define SERVER_TOOLS_RELAY_BROADCAST(message) world.AdminBroadcast(message) - -//IMPLEMENTATION - -#define REBOOT_MODE_NORMAL 0 -#define REBOOT_MODE_HARD 1 -#define REBOOT_MODE_SHUTDOWN 2 - -#define SERVICE_WORLD_PARAM "server_service" -#define SERVICE_VERSION_PARAM "server_service_version" -#define SERVICE_INSTANCE_PARAM "server_instance" -#define SERVICE_PR_TEST_JSON "prtestjob.json" -#define SERVICE_INTERFACE_DLL "TGDreamDaemonBridge.dll" -#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint" - -#define SERVICE_CMD_HARD_REBOOT "hard_reboot" -#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown" -#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce" -#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands" -#define SERVICE_CMD_API_COMPATIBLE "api_compat" -#define SERVICE_CMD_PLAYER_COUNT "client_count" - -#define SERVICE_CMD_PARAM_KEY "serviceCommsKey" -#define SERVICE_CMD_PARAM_COMMAND "command" -#define SERVICE_CMD_PARAM_SENDER "sender" -#define SERVICE_CMD_PARAM_CUSTOM "custom" - -#define SERVICE_JSON_PARAM_HELPTEXT "help_text" -#define SERVICE_JSON_PARAM_ADMINONLY "admin_only" -#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters" - -#define SERVICE_REQUEST_KILL_PROCESS "killme" -#define SERVICE_REQUEST_KILL_PROCESS_SILENT "killmesilent" -#define SERVICE_REQUEST_IRC_BROADCAST "irc" -#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc" -#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot" -#define SERVICE_REQUEST_API_VERSION "api_ver" - -#define SERVICE_RETURN_SUCCESS "SUCCESS" - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +// /tg/station 13 server tools API +#define SERVICE_API_VERSION_STRING "3.2.0.3" + +//CONFIGURATION +//use this define if you want to do configuration outside of this file +#ifndef SERVER_TOOLS_EXTERNAL_CONFIGURATION +//Comment this out once you've filled in the below +#error /tg/station server tools interface unconfigured + +//Required interfaces (fill in with your codebase equivalent): + +//create a global variable named `Name` and set it to `Value` +//These globals must not be modifiable from anywhere outside of the server tools +#define SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(Name, Value) +//Read the value in the global variable `Name` +#define SERVER_TOOLS_READ_GLOBAL(Name) +//Set the value in the global variable `Name` to `Value` +#define SERVER_TOOLS_WRITE_GLOBAL(Name, Value) +//display an announcement `message` from the server to all players +#define SERVER_TOOLS_WORLD_ANNOUNCE(message) +//Write a string `message` to a server log +#define SERVER_TOOLS_LOG(message) +//Notify current in-game administrators of a string `event` +#define SERVER_TOOLS_NOTIFY_ADMINS(event) +//The current amount of connected clients +#define SERVER_TOOLS_CLIENT_COUNT +#endif + +//Required hooks: + +//Put this somewhere in /world/New() that is always run +#define SERVER_TOOLS_ON_NEW ServiceInit() +//Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified +#define SERVER_TOOLS_ON_TOPIC var/service_topic_return = ServiceCommand(params2list(T)); if(service_topic_return) return service_topic_return +//Put at the beginning of world/Reboot(reason) +#define SERVER_TOOLS_ON_REBOOT ServiceReboot() + +//Optional callable functions: + +//Returns the string version of the API +#define SERVER_TOOLS_API_VERSION ServiceAPIVersion() +//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise +//No function below this succeed if this is FALSE +#define SERVER_TOOLS_PRESENT RunningService() +//Gets the current version of the service running the server +#define SERVER_TOOLS_VERSION ServiceVersion() +//Forces a hard reboot of BYOND by ending the process +//unlike del(world) clients will try to reconnect +//If the service has not requested a shutdown, the world will reboot shortly after +#define SERVER_TOOLS_REBOOT_BYOND(silent) world.ServiceEndProcess(silent) +/* + Gets the list of any testmerged github pull requests + + "[PR Number]" => list( + "title" -> PR title + "commit" -> Full hash of commit merged + "author" -> Github username of the author of the PR + ) +*/ +#define SERVER_TOOLS_PR_LIST GetTestMerges() +//Sends a message to connected game chats +#define SERVER_TOOLS_CHAT_BROADCAST(message) world.ChatBroadcast(message) +//Sends a message to connected admin chats +#define SERVER_TOOLS_RELAY_BROADCAST(message) world.AdminBroadcast(message) + +//IMPLEMENTATION + +#define REBOOT_MODE_NORMAL 0 +#define REBOOT_MODE_HARD 1 +#define REBOOT_MODE_SHUTDOWN 2 + +#define SERVICE_WORLD_PARAM "server_service" +#define SERVICE_VERSION_PARAM "server_service_version" +#define SERVICE_INSTANCE_PARAM "server_instance" +#define SERVICE_PR_TEST_JSON "prtestjob.json" +#define SERVICE_INTERFACE_DLL "TGDreamDaemonBridge.dll" +#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint" + +#define SERVICE_CMD_HARD_REBOOT "hard_reboot" +#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown" +#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce" +#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands" +#define SERVICE_CMD_API_COMPATIBLE "api_compat" +#define SERVICE_CMD_PLAYER_COUNT "client_count" + +#define SERVICE_CMD_PARAM_KEY "serviceCommsKey" +#define SERVICE_CMD_PARAM_COMMAND "command" +#define SERVICE_CMD_PARAM_SENDER "sender" +#define SERVICE_CMD_PARAM_CUSTOM "custom" + +#define SERVICE_JSON_PARAM_HELPTEXT "help_text" +#define SERVICE_JSON_PARAM_ADMINONLY "admin_only" +#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters" + +#define SERVICE_REQUEST_KILL_PROCESS "killme" +#define SERVICE_REQUEST_KILL_PROCESS_SILENT "killmesilent" +#define SERVICE_REQUEST_IRC_BROADCAST "irc" +#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc" +#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot" +#define SERVICE_REQUEST_API_VERSION "api_ver" + +#define SERVICE_RETURN_SUCCESS "SUCCESS" + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/DMAPI/st_commands.dm b/legacy/V3/DMAPI/st_commands.dm similarity index 100% rename from DMAPI/st_commands.dm rename to legacy/V3/DMAPI/st_commands.dm diff --git a/DMAPI/st_interface.dm b/legacy/V3/DMAPI/st_interface.dm similarity index 100% rename from DMAPI/st_interface.dm rename to legacy/V3/DMAPI/st_interface.dm diff --git a/legacy/V3/README.md b/legacy/V3/README.md new file mode 100644 index 0000000000..fd2e6d2d93 --- /dev/null +++ b/legacy/V3/README.md @@ -0,0 +1,240 @@ +# Tgstation Toolkit: + +[![Build status](https://ci.appveyor.com/api/projects/status/7t1h7bvuha0p9j5f/branch/master?svg=true)](https://ci.appveyor.com/project/Cyberboss/tgstation-server-tools/branch/master) [![Build Status](https://travis-ci.org/tgstation/tgstation-server.svg?branch=master)](https://travis-ci.org/tgstation/tgstation-server) [![codecov](https://codecov.io/gh/tgstation/tgstation-server/branch/master/graph/badge.svg)](https://codecov.io/gh/tgstation/tgstation-server) [![Waffle.io - Columns and their card count](https://badge.waffle.io/tgstation/tgstation-server.png?columns=all)](https://waffle.io/tgstation/tgstation-server?utm_source=badge) + +[![GitHub license](https://img.shields.io/github/license/tgstation/tgstation-server.svg)](https://github.com/tgstation/tgstation-server/blob/master/LICENSE) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tgstation/tgstation-server.svg)](http://isitmaintained.com/project/tgstation/tgstation-server "Average time to resolve an issue") [![NuGet version](https://img.shields.io/nuget/v/TGServiceInterface.svg)](https://www.nuget.org/packages/TGServiceInterface) + +[![forthebadge](http://forthebadge.com/images/badges/made-with-c-sharp.svg)](http://forthebadge.com) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) + +[![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg)](http://forthebadge.com) + + +This is a toolset to manage a production server of /tg/Station13 (and its forks). It includes the ability to update the server without having to stop or shutdown the server (the update will take effect next round) the ability start the server and restart it if it crashes, as well as systems for fixing errors and merging GitHub Pull Requests locally. + +Generally, updates force a live tracking of the configured git repo, resetting local modifications. If you plan to make modifications, set up a new git repo to store your version of the code in, and point this script to that in the config (explained below). This can be on github or a local repo using file:/// urls. + +Requires python 2.7/3.6 to be installed for changelog generation + +### Legacy Server +* The old cmd script server can be found in the legacy tree + + +## Installing +1. Either compile from source (requires .NET Framework 4.5.2, Nuget, and WiX toolset 4.0) or download the latest [release from github](https://github.com/tgstation/tgstation-server-tools/releases) +1. Unzip setup files +1. Run the installer or use the console server + +## Installing (GUI): +1. Launch `TGControlPanel.exe` as an administrator. A shortcut can be found on your desktop +1. Use the `Create Instance` button to create a new server instance +1. Go to the `Repository` Tab and set the remote address and branch of the git you with to track +1. Hit the clone button +1. While waiting go to the `BYOND` tab and install the BYOND version you wish +1. You may also configure an IRC and/or discord bot for the server on the chat tab +1. Once the clone is complete you may set up a committer identity, user name, and password on the `Repository` tab for pushing changelog updates +1. Go to the `Server` tab and click the `Initialize Game Folders` button +1. Optionally change the `Project Path` Setting from tgstation to wherever the dme/dmb pair are in your repository +1. Optionally tick the Autostart box if you wish to have your server start with Windows +1. When game folder initialization is complete, click the `Copy from Repo and Compile` option + +## Installing (CL example): +This process is identical to the above steps in command line mode. You can always learn more about a command using `?` i.e. `repo ?` +1. Launch TGCommandLine.exe as an administrator (running with no parameters puts you in interactive mode) +1. `service set-python-path C:\Python27` +1. `service create-instance "TGS" D:\tgstation` +1. `instance` And enter `TGS`. If you aren't using interactive mode, the following commands must be suffixed with `--instance TGS` +1. `repo setup https://github.com/tgstation/tgstation master` +1. `byond update 511.1385` +1. `irc nick TGS3Test` +1. `irc set-auth-mode channel-mode` +1. `irc set-auth-level %` +1. `irc setup-auth NickServ "id hunter2"` Yes this is the real password, please use it only for testing +1. `irc join botbus dev` +1. `irc join botbus admin` +1. `irc join botbus wd` +1. `irc join botbus game` +1. `irc enable` +1. `discord set-token Rjfa93jlksjfj934jlkjasf8a08wfl.asdjfj08e44` See https://discordapp.com/developers/docs/topics/oauth2#bots +1. `discord set-auth-mode role-id` +1. `discord addmin 192837419273409` See how to get a role id: https://www.reddit.com/r/discordapp/comments/5bezg2/role_id/. Note that if you `discord set-auth-mode user-id` you'll need to use user ids (Enable developer mode, right click user, `Copy ID`) +1. `discord join 12341234453235 dev` This is a channel id (Enable developer mode, right click channel, `Copy ID`) +1. `discord join 34563456344245 dev` +1. `discord join 23452362574456 admin` +1. `discord join 23452362574456 wd` +1. `discord join 53457345736788 game` +1. `discord enable` +1. `repo set-name tgstation-server` These two lines specify the changelog's committer identity. They are not mirrored in the GUI +1. `repo set-email tgstation-server@tgstation13.org` +1. `repo set-credentials` And follow the prompts +1. `dm project-name tgstation` +1. `dd autostart on` +1. `repo status` To check the clone job status +1. `dm initialize --wait` +1. `dd start` + +## Setting up Multi-User + +1. Create windows accounts for those you wish to have access to the service +1. Join them in a common windows group +1. Run TGCommandLine.exe as an administrator +1. `admin set-group --instance ""` + +Note: Due to internal functionality, a user who has access to at least one server instance will be able to view the metadata (Name, path, Logging ID, and Enabled status) of all instances. + +## Setting up Remote access + +1. Obtain an SSL certificate to secure the connection (this is beyond the scope of this guide) +1. Either stick with the default port `38607` or change it with `admin set-port ` +1. [Bind the SSL certificate to the port](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate) + - e.g. `netsh http add sslcert ipport=0.0.0.0: certhash= appid={F32EDA25-0855-411C-AF5E-F0D042917E2D}` + - The `appid` GUID actually doesn't matter, but for sanity, you should use the GUID of TGServerService.exe as printed above + - Power shell users remember to quit the appid: `netsh http add sslcert ipport=0.0.0.0: certhash= appid="{F32EDA25-0855-411C-AF5E-F0D042917E2D}"` as {} has special meaning in powershell +1. Ensure the port can be acccessed from the internet +1. Log in from any computer using a username and password from the service computer in either the CLI or GUI + +## Updating + +The service supports updates while running a DreamDaemon instance. Simply install an updated version as you normally would and process ownership will transfer smoothly to the new service. + +### Folders and Files (None of these should be touched): +* `Game//` + * This will house two copies of the game code, one for updating and one for live. When updating, it will automatically swap them. + +* `Static/` + * This contains the `data/` and `config/` folders from the code. They are stored here and a symbolic link is created in the `Game` folders pointing to here. + * Resetting the repository will create a backup of this and reinitalize it + +* `Game/Live/` + * This is a symbolic link pointing to current "live" folder. + * When the server is updated, we just point this to the updating folder so that the update takes place next round. + +* `Diagnostics` + * This contains various timestamped diagnostic information for DreamDaemon invocations + +* `Repository/` + * This contains the actual git repository, all changes in here will be overwritten during update operations. + +* `RepoKey/` + * This contains ssh key information for automatic changelog pushing. + +* `EventHandlers/` + * This contains batch files that run after certain events, currently only precompile and postcompile events are implemented. If you'd like an event handler you should create a file in this folder with the following name: `{event_name}.bat` for example: if I want an event handler for precompile, I would name it `precompile.bat`. + +* `BYOND/` + * This contains the actual BYOND installation the server uses + +* `BYOND_staging/` + * This appears when a BYOND update is queued but can't currently be applied due to usage of the current BYOND version. It will be applied at the first possible moment. Restarting the service deletes this folder + +* `BYOND_revision.zip` + * This is a queued update downloaded from BYOND, it will be unzipped into BYOND_staging and deleted. Restarting the service deletes this file + +* `TGDreamDaemonBridge.dll` + * This is the .dll the TGS3 API `call()()`s into to RPC the server instance that runs it + * Instance -> DreamDaemon communication is achieved via `world/Topic()` + +* `prtestjob.json` + * This contains information about current test merged pull requests in the Repository folder + +* `TGS3.json` + * This is a copy of `TGS3.json` from the Repository. If a repostory change creates differences between the two, update operations will be blocked until the user confirms they want to change it + +* `Instance.json` + * The internal configuration settings for a server instance. Note that access to this file bypasses API user restrictions + +### Codebase integration +To get the TGS3 API for your code base, import the 3 .dm files in the `DMAPI` folder into your include structure, then fill out the configuration as documented in the comments of server_tools.dm. Then, you may want to add a `TGS3.json` file to specify any static directories and .dlls your codebase uses, along with the optional changelog compile options. Any changes to `TGS3.json` will block compiler operations until a server operator manually approves them. + +### Starting the game server: +To run the game server, open the `Server` tab of the control panel and click either `Start` + +It will restart the game server if it shutdowns for any reason, giving up after 5 tries in under a minute + +### Updating the server: +To update the server, open the `Server` tab of the control panel. Click `Update Server`. (it will git pull, compile, all that jazz). Note that this button won't clear test merges. + +(Note: Updating automatically does a code reset, clearing ALL changes to the local git repo, including manual changes (This will not change any data in the `Static/` folder)) + +Updates do not require the server to be shutdown, changes will apply next round if the server is currently running. + +All DM compilation will log to the server what commit they happened at and create a backup tag in the local repository. + +### Locally merge GitHub Pull Requests (PR test merge): +This feature currently only works if github is the remote (git server). + +Running these will merge the pull request then recompile the server, changes take effect the next round if the server is currently running. + +There are two flavors to this. +* The manual method is to use the repo page's merge PR button for fine grain control over PR merging. Each time this button is pressed, the latest commit of the specified PR# will be merged into the repo +* The managed method is the Server page's `Test Merge Manager` which uses the GitHub API to get information about available PRs. + * Open PRs are listed by default + * Currently test merged PRs are checked off and listed at the top when it's opened +* If a PR contains a label that contains the text `test` (case-insensitive) it will be listed on top and marked as `TESTING REQUESTED` + * If a PR has been updated since it was test merged, two entries for it will appear. One listed as `OUTDATED` and specifying the commit it was merged at + * You can change the initial update action of the server with the radio buttons in the bottom left. `Update to Remote` fully resets and updates the server before merging PRs. `Update To Origin` does the same based off the local repository's `origin` remote. `No Update` will merge the PRs without any prior action + * Checking off PRs here and then hitting apply will run the selected update action, optionally generate and push a changelog (see below), merge the PRs, and then compile. `Update Server` will keep any active merged PRs until they are manually removed or merged on the `origin` remote. + * Given that the control panel uses GitHub's API to populate the `Test Merge Manager` you may be prompted for your credentials if you make too many requests. This will create a personal access token with public access on your account specifying its use to bypass the GitHub API rate limit. + +You can clear all active test merges using `Reset to Origin Branch` in the `Repository` tab and the using `Copy from Repo and Compile` in the server tab (explained below). You can also use the `Reset All Test Merges` button on the server tab for a concise solution. + +### The Compiler +* `Server` -> `Copy from Repo and Compile` + * Copies the local repository code, compiles it, and stages it to apply next round +* `Server` -> `Initialize Game Folders` + * Requires the server not be running, rebuilds the `Game` folder and then does `Copy from Repo and Compile` + * Required on first setup + +### Starting everything when the computer/server boots +Just tick the `Autostart` option in the `Server` tab or run `dd autostart on` on the command line. As it's a windows service, it will automatically run without having to log ing + +### Enabling the BYOND Webclient +Just tick the `Webclient` option in the `Server` tab or run `dd webclient on` on the command line. + +### Static Configuration +* The `Static Files` page of the control panel lets you modify all files in directories you specified in `TGS3.json` +* The files are modified here using the Windows credentials of the active user, feel free to manually set ACLs on them +* You may optionally rebuild the entire `Static` folder from the repository using the `Recreate Static Directory` button. This will copy the original files from the repository based on the current `TGS3.json`. This requires DreamDaemon not be running + +### Modifying Your Code + +Any `.dm` files included in the root level of the `Static` directory are automatically copied over and included before anything else in your `.dme` for compilation. Use this to configure compile options as you see fit. + +### Moving, Renaming, and Detaching instances + +* Instances can be renamed, but this requires a temporary offlining of them (this includes interface access, DreamDaemon, and chat bots) +* Detaching an instance simply removes it from the main server's configuration and control, leaving it free for the user to manipulate +* Importing an instance will work as long as + 1. No other instance currently in the server has the same name + 1. It is not the same path as another instance + Note that importing an instance as a different windows user (this is always different across machines) will result in a loss of chat configuration due to the encryption scheme + +### Viewing Server Logs +* Service logs are stored in the Windows event viewer under `Windows Logs` -> `Application`. You'll need to filter this list for `TG Station Server` +* Every event type is keyed with an ID. A complete listing of these IDs and their purpose can be found [here](https://github.com/tgstation/tgstation-server/blob/master/TGS.Server/EventID.cs). Event IDs from different instances are offset by the logging ID the instance was assigned when it was started. +* You can also import the custom view `View TGS3 Logs.xml` in this folder to have them automatically filtered +* Servers running in console mode only use std_out + +### Enabling upstream changelog generation +* The repository will automatically create an ssh version of the initial origin remote and can optionally push generated changelogs to your git through it +* The repository can only authenticate using ssh public key authentication +* To enable this feature, simply create `public_key.txt` and `private_key.txt` in a folder called RepoKey in the server directory +* The private key must be in `-----BEGIN RSA PRIVATE KEY-----` format and the public key must be in `ssh-rsa` format. You can generate a keypair like this using the converter in the dropdown menu of PuTTYGen. See github guidelines for setting this up here: https://help.github.com/articles/connecting-to-github-with-ssh/ +* The server will be able to read these files regardless of their permissions, so the responsibility is on you to set their ACL's so they can't be read by those that shouldn't + +### Synchronized test merge commits +* An instance can push branchless test merge commits to GitHub if the `RepoKey` folder is setup +* This pushes all test merge commits to the remote branch `___TGS3TempBranch` and then deletes it +* This provides public reference information about the test merge commit and time since it will appear in the PR in questing +* To enable this, check the `Sync Commits` button on the `Repository` page of the control panel or run `repo push-testmerges on --instance ""` from TGCommandLine + +## CONTRIBUTING + +* See [CONTRIBUTING.md](https://github.com/tgstation/tgstation-server/blob/master/.github/CONTRIBUTING.md) + +## LICENSING + +* The DM API for the project is licensed under the MIT license. +* The /tg/station 13 icon is licensed under [Creative Commons 3.0 BY-SA](http://creativecommons.org/licenses/by-sa/3.0/). +* The remainder of the project is licensed under [GNU AGPL v3](http://www.gnu.org/licenses/agpl-3.0.html) + +See the bottom of each file in the /DMAPI tree for the MIT license diff --git a/TGS.CommandLine/AdminCommands.cs b/legacy/V3/TGS.CommandLine/AdminCommands.cs similarity index 100% rename from TGS.CommandLine/AdminCommands.cs rename to legacy/V3/TGS.CommandLine/AdminCommands.cs diff --git a/TGS.CommandLine/App.config b/legacy/V3/TGS.CommandLine/App.config similarity index 100% rename from TGS.CommandLine/App.config rename to legacy/V3/TGS.CommandLine/App.config diff --git a/TGS.CommandLine/BYONDCommands.cs b/legacy/V3/TGS.CommandLine/BYONDCommands.cs similarity index 100% rename from TGS.CommandLine/BYONDCommands.cs rename to legacy/V3/TGS.CommandLine/BYONDCommands.cs diff --git a/TGS.CommandLine/ChatCommands.cs b/legacy/V3/TGS.CommandLine/ChatCommands.cs similarity index 100% rename from TGS.CommandLine/ChatCommands.cs rename to legacy/V3/TGS.CommandLine/ChatCommands.cs diff --git a/TGS.CommandLine/ConfigCommands.cs b/legacy/V3/TGS.CommandLine/ConfigCommands.cs similarity index 100% rename from TGS.CommandLine/ConfigCommands.cs rename to legacy/V3/TGS.CommandLine/ConfigCommands.cs diff --git a/TGS.CommandLine/ConsoleCommand.cs b/legacy/V3/TGS.CommandLine/ConsoleCommand.cs similarity index 100% rename from TGS.CommandLine/ConsoleCommand.cs rename to legacy/V3/TGS.CommandLine/ConsoleCommand.cs diff --git a/TGS.CommandLine/DDCommands.cs b/legacy/V3/TGS.CommandLine/DDCommands.cs similarity index 100% rename from TGS.CommandLine/DDCommands.cs rename to legacy/V3/TGS.CommandLine/DDCommands.cs diff --git a/TGS.CommandLine/DMCommands.cs b/legacy/V3/TGS.CommandLine/DMCommands.cs similarity index 100% rename from TGS.CommandLine/DMCommands.cs rename to legacy/V3/TGS.CommandLine/DMCommands.cs diff --git a/TGS.CommandLine/Program.cs b/legacy/V3/TGS.CommandLine/Program.cs similarity index 100% rename from TGS.CommandLine/Program.cs rename to legacy/V3/TGS.CommandLine/Program.cs diff --git a/TGS.CommandLine/Properties/AssemblyInfo.cs b/legacy/V3/TGS.CommandLine/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.CommandLine/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.CommandLine/Properties/AssemblyInfo.cs diff --git a/TGS.CommandLine/RepoCommands.cs b/legacy/V3/TGS.CommandLine/RepoCommands.cs similarity index 100% rename from TGS.CommandLine/RepoCommands.cs rename to legacy/V3/TGS.CommandLine/RepoCommands.cs diff --git a/TGS.CommandLine/RootCommands.cs b/legacy/V3/TGS.CommandLine/RootCommands.cs similarity index 100% rename from TGS.CommandLine/RootCommands.cs rename to legacy/V3/TGS.CommandLine/RootCommands.cs diff --git a/TGS.CommandLine/ServiceCommands.cs b/legacy/V3/TGS.CommandLine/ServiceCommands.cs similarity index 100% rename from TGS.CommandLine/ServiceCommands.cs rename to legacy/V3/TGS.CommandLine/ServiceCommands.cs diff --git a/TGS.CommandLine/TGS.CommandLine.csproj b/legacy/V3/TGS.CommandLine/TGS.CommandLine.csproj similarity index 100% rename from TGS.CommandLine/TGS.CommandLine.csproj rename to legacy/V3/TGS.CommandLine/TGS.CommandLine.csproj diff --git a/TGS.CommandLine/tgs.ico b/legacy/V3/TGS.CommandLine/tgs.ico similarity index 100% rename from TGS.CommandLine/tgs.ico rename to legacy/V3/TGS.CommandLine/tgs.ico diff --git a/TGS.ControlPanel/App.config b/legacy/V3/TGS.ControlPanel/App.config similarity index 100% rename from TGS.ControlPanel/App.config rename to legacy/V3/TGS.ControlPanel/App.config diff --git a/TGS.ControlPanel/ControlPanel/ByondPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ByondPage.cs similarity index 100% rename from TGS.ControlPanel/ControlPanel/ByondPage.cs rename to legacy/V3/TGS.ControlPanel/ControlPanel/ByondPage.cs diff --git a/TGS.ControlPanel/ControlPanel/ChatPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ChatPage.cs similarity index 100% rename from TGS.ControlPanel/ControlPanel/ChatPage.cs rename to legacy/V3/TGS.ControlPanel/ControlPanel/ChatPage.cs diff --git a/TGS.ControlPanel/ControlPanel/ControlPanel.Designer.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.Designer.cs similarity index 100% rename from TGS.ControlPanel/ControlPanel/ControlPanel.Designer.cs rename to legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.Designer.cs diff --git a/TGS.ControlPanel/ControlPanel/ControlPanel.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.cs similarity index 100% rename from TGS.ControlPanel/ControlPanel/ControlPanel.cs rename to legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.cs diff --git a/TGS.ControlPanel/ControlPanel/ControlPanel.resx b/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.resx similarity index 100% rename from TGS.ControlPanel/ControlPanel/ControlPanel.resx rename to legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.resx diff --git a/TGS.ControlPanel/ControlPanel/RepoPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/RepoPage.cs similarity index 100% rename from TGS.ControlPanel/ControlPanel/RepoPage.cs rename to legacy/V3/TGS.ControlPanel/ControlPanel/RepoPage.cs diff --git a/TGS.ControlPanel/ControlPanel/ServerPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ServerPage.cs similarity index 100% rename from TGS.ControlPanel/ControlPanel/ServerPage.cs rename to legacy/V3/TGS.ControlPanel/ControlPanel/ServerPage.cs diff --git a/TGS.ControlPanel/ControlPanel/StaticPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/StaticPage.cs similarity index 100% rename from TGS.ControlPanel/ControlPanel/StaticPage.cs rename to legacy/V3/TGS.ControlPanel/ControlPanel/StaticPage.cs diff --git a/TGS.ControlPanel/CountedForm.cs b/legacy/V3/TGS.ControlPanel/CountedForm.cs similarity index 100% rename from TGS.ControlPanel/CountedForm.cs rename to legacy/V3/TGS.ControlPanel/CountedForm.cs diff --git a/TGS.ControlPanel/GitHubLoginPrompt.Designer.cs b/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.Designer.cs similarity index 100% rename from TGS.ControlPanel/GitHubLoginPrompt.Designer.cs rename to legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.Designer.cs diff --git a/TGS.ControlPanel/GitHubLoginPrompt.cs b/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.cs similarity index 100% rename from TGS.ControlPanel/GitHubLoginPrompt.cs rename to legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.cs diff --git a/TGS.ControlPanel/GitHubLoginPrompt.resx b/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.resx similarity index 100% rename from TGS.ControlPanel/GitHubLoginPrompt.resx rename to legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.resx diff --git a/TGS.ControlPanel/InstanceSelector.Designer.cs b/legacy/V3/TGS.ControlPanel/InstanceSelector.Designer.cs similarity index 100% rename from TGS.ControlPanel/InstanceSelector.Designer.cs rename to legacy/V3/TGS.ControlPanel/InstanceSelector.Designer.cs diff --git a/TGS.ControlPanel/InstanceSelector.cs b/legacy/V3/TGS.ControlPanel/InstanceSelector.cs similarity index 100% rename from TGS.ControlPanel/InstanceSelector.cs rename to legacy/V3/TGS.ControlPanel/InstanceSelector.cs diff --git a/TGS.ControlPanel/InstanceSelector.resx b/legacy/V3/TGS.ControlPanel/InstanceSelector.resx similarity index 100% rename from TGS.ControlPanel/InstanceSelector.resx rename to legacy/V3/TGS.ControlPanel/InstanceSelector.resx diff --git a/TGS.ControlPanel/Login.Designer.cs b/legacy/V3/TGS.ControlPanel/Login.Designer.cs similarity index 100% rename from TGS.ControlPanel/Login.Designer.cs rename to legacy/V3/TGS.ControlPanel/Login.Designer.cs diff --git a/TGS.ControlPanel/Login.cs b/legacy/V3/TGS.ControlPanel/Login.cs similarity index 100% rename from TGS.ControlPanel/Login.cs rename to legacy/V3/TGS.ControlPanel/Login.cs diff --git a/TGS.ControlPanel/Login.resx b/legacy/V3/TGS.ControlPanel/Login.resx similarity index 100% rename from TGS.ControlPanel/Login.resx rename to legacy/V3/TGS.ControlPanel/Login.resx diff --git a/TGS.ControlPanel/Program.cs b/legacy/V3/TGS.ControlPanel/Program.cs similarity index 100% rename from TGS.ControlPanel/Program.cs rename to legacy/V3/TGS.ControlPanel/Program.cs diff --git a/TGS.ControlPanel/Properties/AssemblyInfo.cs b/legacy/V3/TGS.ControlPanel/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.ControlPanel/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.ControlPanel/Properties/AssemblyInfo.cs diff --git a/TGS.ControlPanel/Properties/Settings.Designer.cs b/legacy/V3/TGS.ControlPanel/Properties/Settings.Designer.cs similarity index 100% rename from TGS.ControlPanel/Properties/Settings.Designer.cs rename to legacy/V3/TGS.ControlPanel/Properties/Settings.Designer.cs diff --git a/TGS.ControlPanel/Properties/Settings.settings b/legacy/V3/TGS.ControlPanel/Properties/Settings.settings similarity index 100% rename from TGS.ControlPanel/Properties/Settings.settings rename to legacy/V3/TGS.ControlPanel/Properties/Settings.settings diff --git a/TGS.ControlPanel/ServerOpForm.cs b/legacy/V3/TGS.ControlPanel/ServerOpForm.cs similarity index 100% rename from TGS.ControlPanel/ServerOpForm.cs rename to legacy/V3/TGS.ControlPanel/ServerOpForm.cs diff --git a/TGS.ControlPanel/TGS.ControlPanel.csproj b/legacy/V3/TGS.ControlPanel/TGS.ControlPanel.csproj similarity index 100% rename from TGS.ControlPanel/TGS.ControlPanel.csproj rename to legacy/V3/TGS.ControlPanel/TGS.ControlPanel.csproj diff --git a/TGS.ControlPanel/TestMergeManager.Designer.cs b/legacy/V3/TGS.ControlPanel/TestMergeManager.Designer.cs similarity index 100% rename from TGS.ControlPanel/TestMergeManager.Designer.cs rename to legacy/V3/TGS.ControlPanel/TestMergeManager.Designer.cs diff --git a/TGS.ControlPanel/TestMergeManager.cs b/legacy/V3/TGS.ControlPanel/TestMergeManager.cs similarity index 100% rename from TGS.ControlPanel/TestMergeManager.cs rename to legacy/V3/TGS.ControlPanel/TestMergeManager.cs diff --git a/TGS.ControlPanel/TestMergeManager.resx b/legacy/V3/TGS.ControlPanel/TestMergeManager.resx similarity index 100% rename from TGS.ControlPanel/TestMergeManager.resx rename to legacy/V3/TGS.ControlPanel/TestMergeManager.resx diff --git a/TGS.ControlPanel/packages.config b/legacy/V3/TGS.ControlPanel/packages.config similarity index 100% rename from TGS.ControlPanel/packages.config rename to legacy/V3/TGS.ControlPanel/packages.config diff --git a/TGS.ControlPanel/tgs.ico b/legacy/V3/TGS.ControlPanel/tgs.ico similarity index 100% rename from TGS.ControlPanel/tgs.ico rename to legacy/V3/TGS.ControlPanel/tgs.ico diff --git a/TGS.Installer.UI/App.config b/legacy/V3/TGS.Installer.UI/App.config similarity index 100% rename from TGS.Installer.UI/App.config rename to legacy/V3/TGS.Installer.UI/App.config diff --git a/TGS.Installer.UI/FodyWeavers.xml b/legacy/V3/TGS.Installer.UI/FodyWeavers.xml similarity index 100% rename from TGS.Installer.UI/FodyWeavers.xml rename to legacy/V3/TGS.Installer.UI/FodyWeavers.xml diff --git a/TGS.Installer.UI/Main.Designer.cs b/legacy/V3/TGS.Installer.UI/Main.Designer.cs similarity index 100% rename from TGS.Installer.UI/Main.Designer.cs rename to legacy/V3/TGS.Installer.UI/Main.Designer.cs diff --git a/TGS.Installer.UI/Main.cs b/legacy/V3/TGS.Installer.UI/Main.cs similarity index 100% rename from TGS.Installer.UI/Main.cs rename to legacy/V3/TGS.Installer.UI/Main.cs diff --git a/TGS.Installer.UI/Main.resx b/legacy/V3/TGS.Installer.UI/Main.resx similarity index 100% rename from TGS.Installer.UI/Main.resx rename to legacy/V3/TGS.Installer.UI/Main.resx diff --git a/TGS.Installer.UI/Program.cs b/legacy/V3/TGS.Installer.UI/Program.cs similarity index 100% rename from TGS.Installer.UI/Program.cs rename to legacy/V3/TGS.Installer.UI/Program.cs diff --git a/TGS.Installer.UI/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Installer.UI/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.Installer.UI/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.Installer.UI/Properties/AssemblyInfo.cs diff --git a/TGS.Installer.UI/Properties/Resources.Designer.cs b/legacy/V3/TGS.Installer.UI/Properties/Resources.Designer.cs similarity index 100% rename from TGS.Installer.UI/Properties/Resources.Designer.cs rename to legacy/V3/TGS.Installer.UI/Properties/Resources.Designer.cs diff --git a/TGS.Installer.UI/Properties/Resources.resx b/legacy/V3/TGS.Installer.UI/Properties/Resources.resx similarity index 100% rename from TGS.Installer.UI/Properties/Resources.resx rename to legacy/V3/TGS.Installer.UI/Properties/Resources.resx diff --git a/TGS.Installer.UI/TGS.Installer.UI.csproj b/legacy/V3/TGS.Installer.UI/TGS.Installer.UI.csproj similarity index 100% rename from TGS.Installer.UI/TGS.Installer.UI.csproj rename to legacy/V3/TGS.Installer.UI/TGS.Installer.UI.csproj diff --git a/TGS.Installer.UI/app.manifest b/legacy/V3/TGS.Installer.UI/app.manifest similarity index 100% rename from TGS.Installer.UI/app.manifest rename to legacy/V3/TGS.Installer.UI/app.manifest diff --git a/TGS.Installer.UI/packages.config b/legacy/V3/TGS.Installer.UI/packages.config similarity index 100% rename from TGS.Installer.UI/packages.config rename to legacy/V3/TGS.Installer.UI/packages.config diff --git a/TGS.Installer.UI/tgs.ico b/legacy/V3/TGS.Installer.UI/tgs.ico similarity index 100% rename from TGS.Installer.UI/tgs.ico rename to legacy/V3/TGS.Installer.UI/tgs.ico diff --git a/TGS.Installer/Product.wxs b/legacy/V3/TGS.Installer/Product.wxs similarity index 100% rename from TGS.Installer/Product.wxs rename to legacy/V3/TGS.Installer/Product.wxs diff --git a/TGS.Installer/TGS.Installer.wixproj b/legacy/V3/TGS.Installer/TGS.Installer.wixproj similarity index 100% rename from TGS.Installer/TGS.Installer.wixproj rename to legacy/V3/TGS.Installer/TGS.Installer.wixproj diff --git a/TGS.Installer/packages.config b/legacy/V3/TGS.Installer/packages.config similarity index 100% rename from TGS.Installer/packages.config rename to legacy/V3/TGS.Installer/packages.config diff --git a/TGS.Interface.Bridge/DreamDaemonBridge.cs b/legacy/V3/TGS.Interface.Bridge/DreamDaemonBridge.cs similarity index 100% rename from TGS.Interface.Bridge/DreamDaemonBridge.cs rename to legacy/V3/TGS.Interface.Bridge/DreamDaemonBridge.cs diff --git a/TGS.Interface.Bridge/FodyWeavers.xml b/legacy/V3/TGS.Interface.Bridge/FodyWeavers.xml similarity index 100% rename from TGS.Interface.Bridge/FodyWeavers.xml rename to legacy/V3/TGS.Interface.Bridge/FodyWeavers.xml diff --git a/TGS.Interface.Bridge/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Interface.Bridge/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.Interface.Bridge/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.Interface.Bridge/Properties/AssemblyInfo.cs diff --git a/TGS.Interface.Bridge/TGS.Interface.Bridge.csproj b/legacy/V3/TGS.Interface.Bridge/TGS.Interface.Bridge.csproj similarity index 100% rename from TGS.Interface.Bridge/TGS.Interface.Bridge.csproj rename to legacy/V3/TGS.Interface.Bridge/TGS.Interface.Bridge.csproj diff --git a/TGS.Interface.Bridge/packages.config b/legacy/V3/TGS.Interface.Bridge/packages.config similarity index 100% rename from TGS.Interface.Bridge/packages.config rename to legacy/V3/TGS.Interface.Bridge/packages.config diff --git a/TGS.Interface/AuthenticationHeaderApplicator.cs b/legacy/V3/TGS.Interface/AuthenticationHeaderApplicator.cs similarity index 100% rename from TGS.Interface/AuthenticationHeaderApplicator.cs rename to legacy/V3/TGS.Interface/AuthenticationHeaderApplicator.cs diff --git a/TGS.Interface/ChatSetupInfo.cs b/legacy/V3/TGS.Interface/ChatSetupInfo.cs similarity index 100% rename from TGS.Interface/ChatSetupInfo.cs rename to legacy/V3/TGS.Interface/ChatSetupInfo.cs diff --git a/TGS.Interface/Client.cs b/legacy/V3/TGS.Interface/Client.cs similarity index 100% rename from TGS.Interface/Client.cs rename to legacy/V3/TGS.Interface/Client.cs diff --git a/TGS.Interface/Command.cs b/legacy/V3/TGS.Interface/Command.cs similarity index 100% rename from TGS.Interface/Command.cs rename to legacy/V3/TGS.Interface/Command.cs diff --git a/TGS.Interface/Components/Administration.cs b/legacy/V3/TGS.Interface/Components/Administration.cs similarity index 100% rename from TGS.Interface/Components/Administration.cs rename to legacy/V3/TGS.Interface/Components/Administration.cs diff --git a/TGS.Interface/Components/Byond.cs b/legacy/V3/TGS.Interface/Components/Byond.cs similarity index 100% rename from TGS.Interface/Components/Byond.cs rename to legacy/V3/TGS.Interface/Components/Byond.cs diff --git a/TGS.Interface/Components/Chat.cs b/legacy/V3/TGS.Interface/Components/Chat.cs similarity index 100% rename from TGS.Interface/Components/Chat.cs rename to legacy/V3/TGS.Interface/Components/Chat.cs diff --git a/TGS.Interface/Components/Compiler.cs b/legacy/V3/TGS.Interface/Components/Compiler.cs similarity index 100% rename from TGS.Interface/Components/Compiler.cs rename to legacy/V3/TGS.Interface/Components/Compiler.cs diff --git a/TGS.Interface/Components/Config.cs b/legacy/V3/TGS.Interface/Components/Config.cs similarity index 100% rename from TGS.Interface/Components/Config.cs rename to legacy/V3/TGS.Interface/Components/Config.cs diff --git a/TGS.Interface/Components/Connectivity.cs b/legacy/V3/TGS.Interface/Components/Connectivity.cs similarity index 100% rename from TGS.Interface/Components/Connectivity.cs rename to legacy/V3/TGS.Interface/Components/Connectivity.cs diff --git a/TGS.Interface/Components/DreamDaemon.cs b/legacy/V3/TGS.Interface/Components/DreamDaemon.cs similarity index 100% rename from TGS.Interface/Components/DreamDaemon.cs rename to legacy/V3/TGS.Interface/Components/DreamDaemon.cs diff --git a/TGS.Interface/Components/Instance.cs b/legacy/V3/TGS.Interface/Components/Instance.cs similarity index 100% rename from TGS.Interface/Components/Instance.cs rename to legacy/V3/TGS.Interface/Components/Instance.cs diff --git a/TGS.Interface/Components/InstanceManager.cs b/legacy/V3/TGS.Interface/Components/InstanceManager.cs similarity index 100% rename from TGS.Interface/Components/InstanceManager.cs rename to legacy/V3/TGS.Interface/Components/InstanceManager.cs diff --git a/TGS.Interface/Components/Interop.cs b/legacy/V3/TGS.Interface/Components/Interop.cs similarity index 100% rename from TGS.Interface/Components/Interop.cs rename to legacy/V3/TGS.Interface/Components/Interop.cs diff --git a/TGS.Interface/Components/Landing.cs b/legacy/V3/TGS.Interface/Components/Landing.cs similarity index 100% rename from TGS.Interface/Components/Landing.cs rename to legacy/V3/TGS.Interface/Components/Landing.cs diff --git a/TGS.Interface/Components/Repository.cs b/legacy/V3/TGS.Interface/Components/Repository.cs similarity index 100% rename from TGS.Interface/Components/Repository.cs rename to legacy/V3/TGS.Interface/Components/Repository.cs diff --git a/TGS.Interface/Components/Service.cs b/legacy/V3/TGS.Interface/Components/Service.cs similarity index 100% rename from TGS.Interface/Components/Service.cs rename to legacy/V3/TGS.Interface/Components/Service.cs diff --git a/TGS.Interface/Definitions.cs b/legacy/V3/TGS.Interface/Definitions.cs similarity index 100% rename from TGS.Interface/Definitions.cs rename to legacy/V3/TGS.Interface/Definitions.cs diff --git a/TGS.Interface/Enumerations.cs b/legacy/V3/TGS.Interface/Enumerations.cs similarity index 100% rename from TGS.Interface/Enumerations.cs rename to legacy/V3/TGS.Interface/Enumerations.cs diff --git a/TGS.Interface/Helpers.cs b/legacy/V3/TGS.Interface/Helpers.cs similarity index 100% rename from TGS.Interface/Helpers.cs rename to legacy/V3/TGS.Interface/Helpers.cs diff --git a/TGS.Interface/IClient.cs b/legacy/V3/TGS.Interface/IClient.cs similarity index 100% rename from TGS.Interface/IClient.cs rename to legacy/V3/TGS.Interface/IClient.cs diff --git a/TGS.Interface/IInstance.cs b/legacy/V3/TGS.Interface/IInstance.cs similarity index 100% rename from TGS.Interface/IInstance.cs rename to legacy/V3/TGS.Interface/IInstance.cs diff --git a/TGS.Interface/IServer.cs b/legacy/V3/TGS.Interface/IServer.cs similarity index 100% rename from TGS.Interface/IServer.cs rename to legacy/V3/TGS.Interface/IServer.cs diff --git a/TGS.Interface/Instance.cs b/legacy/V3/TGS.Interface/Instance.cs similarity index 100% rename from TGS.Interface/Instance.cs rename to legacy/V3/TGS.Interface/Instance.cs diff --git a/TGS.Interface/InstanceMetadata.cs b/legacy/V3/TGS.Interface/InstanceMetadata.cs similarity index 100% rename from TGS.Interface/InstanceMetadata.cs rename to legacy/V3/TGS.Interface/InstanceMetadata.cs diff --git a/TGS.Interface/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Interface/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.Interface/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.Interface/Properties/AssemblyInfo.cs diff --git a/TGS.Interface/PullRequestInfo.cs b/legacy/V3/TGS.Interface/PullRequestInfo.cs similarity index 100% rename from TGS.Interface/PullRequestInfo.cs rename to legacy/V3/TGS.Interface/PullRequestInfo.cs diff --git a/TGS.Interface/RemoteLoginInfo.cs b/legacy/V3/TGS.Interface/RemoteLoginInfo.cs similarity index 100% rename from TGS.Interface/RemoteLoginInfo.cs rename to legacy/V3/TGS.Interface/RemoteLoginInfo.cs diff --git a/TGS.Interface/RootCommand.cs b/legacy/V3/TGS.Interface/RootCommand.cs similarity index 100% rename from TGS.Interface/RootCommand.cs rename to legacy/V3/TGS.Interface/RootCommand.cs diff --git a/TGS.Interface/Server.cs b/legacy/V3/TGS.Interface/Server.cs similarity index 100% rename from TGS.Interface/Server.cs rename to legacy/V3/TGS.Interface/Server.cs diff --git a/TGS.Interface/TGS.Interface.csproj b/legacy/V3/TGS.Interface/TGS.Interface.csproj similarity index 100% rename from TGS.Interface/TGS.Interface.csproj rename to legacy/V3/TGS.Interface/TGS.Interface.csproj diff --git a/TGS.Interface/TGS.Interface.nuspec b/legacy/V3/TGS.Interface/TGS.Interface.nuspec similarity index 100% rename from TGS.Interface/TGS.Interface.nuspec rename to legacy/V3/TGS.Interface/TGS.Interface.nuspec diff --git a/TGS.Interface/packages.config b/legacy/V3/TGS.Interface/packages.config similarity index 100% rename from TGS.Interface/packages.config rename to legacy/V3/TGS.Interface/packages.config diff --git a/TGS.Interface/tgs.ico b/legacy/V3/TGS.Interface/tgs.ico similarity index 100% rename from TGS.Interface/tgs.ico rename to legacy/V3/TGS.Interface/tgs.ico diff --git a/TGS.Server.Console/App.config b/legacy/V3/TGS.Server.Console/App.config similarity index 100% rename from TGS.Server.Console/App.config rename to legacy/V3/TGS.Server.Console/App.config diff --git a/TGS.Server.Console/Console.cs b/legacy/V3/TGS.Server.Console/Console.cs similarity index 100% rename from TGS.Server.Console/Console.cs rename to legacy/V3/TGS.Server.Console/Console.cs diff --git a/TGS.Server.Console/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Server.Console/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.Server.Console/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.Server.Console/Properties/AssemblyInfo.cs diff --git a/TGS.Server.Console/TGS.Server.Console.csproj b/legacy/V3/TGS.Server.Console/TGS.Server.Console.csproj similarity index 100% rename from TGS.Server.Console/TGS.Server.Console.csproj rename to legacy/V3/TGS.Server.Console/TGS.Server.Console.csproj diff --git a/TGS.Server.Console/app.manifest b/legacy/V3/TGS.Server.Console/app.manifest similarity index 100% rename from TGS.Server.Console/app.manifest rename to legacy/V3/TGS.Server.Console/app.manifest diff --git a/TGS.Server.Console/tgs.ico b/legacy/V3/TGS.Server.Console/tgs.ico similarity index 100% rename from TGS.Server.Console/tgs.ico rename to legacy/V3/TGS.Server.Console/tgs.ico diff --git a/TGS.Server.Service/App.config b/legacy/V3/TGS.Server.Service/App.config similarity index 100% rename from TGS.Server.Service/App.config rename to legacy/V3/TGS.Server.Service/App.config diff --git a/TGS.Server.Service/ProjectInstaller.cs b/legacy/V3/TGS.Server.Service/ProjectInstaller.cs similarity index 100% rename from TGS.Server.Service/ProjectInstaller.cs rename to legacy/V3/TGS.Server.Service/ProjectInstaller.cs diff --git a/TGS.Server.Service/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Server.Service/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.Server.Service/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.Server.Service/Properties/AssemblyInfo.cs diff --git a/TGS.Server.Service/Service.cs b/legacy/V3/TGS.Server.Service/Service.cs similarity index 100% rename from TGS.Server.Service/Service.cs rename to legacy/V3/TGS.Server.Service/Service.cs diff --git a/TGS.Server.Service/TGS.Server.Service.csproj b/legacy/V3/TGS.Server.Service/TGS.Server.Service.csproj similarity index 100% rename from TGS.Server.Service/TGS.Server.Service.csproj rename to legacy/V3/TGS.Server.Service/TGS.Server.Service.csproj diff --git a/TGS.Server.Service/tgs.ico b/legacy/V3/TGS.Server.Service/tgs.ico similarity index 100% rename from TGS.Server.Service/tgs.ico rename to legacy/V3/TGS.Server.Service/tgs.ico diff --git a/TGS.Server/App.config b/legacy/V3/TGS.Server/App.config similarity index 100% rename from TGS.Server/App.config rename to legacy/V3/TGS.Server/App.config diff --git a/TGS.Server/ChatCommands/ByondCommand.cs b/legacy/V3/TGS.Server/ChatCommands/ByondCommand.cs similarity index 100% rename from TGS.Server/ChatCommands/ByondCommand.cs rename to legacy/V3/TGS.Server/ChatCommands/ByondCommand.cs diff --git a/TGS.Server/ChatCommands/ChatCommand.cs b/legacy/V3/TGS.Server/ChatCommands/ChatCommand.cs similarity index 100% rename from TGS.Server/ChatCommands/ChatCommand.cs rename to legacy/V3/TGS.Server/ChatCommands/ChatCommand.cs diff --git a/TGS.Server/ChatCommands/CommandInfo.cs b/legacy/V3/TGS.Server/ChatCommands/CommandInfo.cs similarity index 100% rename from TGS.Server/ChatCommands/CommandInfo.cs rename to legacy/V3/TGS.Server/ChatCommands/CommandInfo.cs diff --git a/TGS.Server/ChatCommands/KekCommand.cs b/legacy/V3/TGS.Server/ChatCommands/KekCommand.cs similarity index 100% rename from TGS.Server/ChatCommands/KekCommand.cs rename to legacy/V3/TGS.Server/ChatCommands/KekCommand.cs diff --git a/TGS.Server/ChatCommands/PullRequestsCommand.cs b/legacy/V3/TGS.Server/ChatCommands/PullRequestsCommand.cs similarity index 100% rename from TGS.Server/ChatCommands/PullRequestsCommand.cs rename to legacy/V3/TGS.Server/ChatCommands/PullRequestsCommand.cs diff --git a/TGS.Server/ChatCommands/RevisionCommand.cs b/legacy/V3/TGS.Server/ChatCommands/RevisionCommand.cs similarity index 100% rename from TGS.Server/ChatCommands/RevisionCommand.cs rename to legacy/V3/TGS.Server/ChatCommands/RevisionCommand.cs diff --git a/TGS.Server/ChatCommands/RootChatCommand.cs b/legacy/V3/TGS.Server/ChatCommands/RootChatCommand.cs similarity index 100% rename from TGS.Server/ChatCommands/RootChatCommand.cs rename to legacy/V3/TGS.Server/ChatCommands/RootChatCommand.cs diff --git a/TGS.Server/ChatCommands/ServerChatCommand.cs b/legacy/V3/TGS.Server/ChatCommands/ServerChatCommand.cs similarity index 100% rename from TGS.Server/ChatCommands/ServerChatCommand.cs rename to legacy/V3/TGS.Server/ChatCommands/ServerChatCommand.cs diff --git a/TGS.Server/ChatCommands/VersionCommand.cs b/legacy/V3/TGS.Server/ChatCommands/VersionCommand.cs similarity index 100% rename from TGS.Server/ChatCommands/VersionCommand.cs rename to legacy/V3/TGS.Server/ChatCommands/VersionCommand.cs diff --git a/TGS.Server/ChatProviders/ChatProvider.cs b/legacy/V3/TGS.Server/ChatProviders/ChatProvider.cs similarity index 100% rename from TGS.Server/ChatProviders/ChatProvider.cs rename to legacy/V3/TGS.Server/ChatProviders/ChatProvider.cs diff --git a/TGS.Server/ChatProviders/DiscordChatProvider.cs b/legacy/V3/TGS.Server/ChatProviders/DiscordChatProvider.cs similarity index 100% rename from TGS.Server/ChatProviders/DiscordChatProvider.cs rename to legacy/V3/TGS.Server/ChatProviders/DiscordChatProvider.cs diff --git a/TGS.Server/ChatProviders/IRCChatProvider.cs b/legacy/V3/TGS.Server/ChatProviders/IRCChatProvider.cs similarity index 100% rename from TGS.Server/ChatProviders/IRCChatProvider.cs rename to legacy/V3/TGS.Server/ChatProviders/IRCChatProvider.cs diff --git a/TGS.Server/DeprecatedInstanceConfig.cs b/legacy/V3/TGS.Server/DeprecatedInstanceConfig.cs similarity index 100% rename from TGS.Server/DeprecatedInstanceConfig.cs rename to legacy/V3/TGS.Server/DeprecatedInstanceConfig.cs diff --git a/TGS.Server/EventID.cs b/legacy/V3/TGS.Server/EventID.cs similarity index 100% rename from TGS.Server/EventID.cs rename to legacy/V3/TGS.Server/EventID.cs diff --git a/TGS.Server/Helpers.cs b/legacy/V3/TGS.Server/Helpers.cs similarity index 100% rename from TGS.Server/Helpers.cs rename to legacy/V3/TGS.Server/Helpers.cs diff --git a/TGS.Server/ILogger.cs b/legacy/V3/TGS.Server/ILogger.cs similarity index 100% rename from TGS.Server/ILogger.cs rename to legacy/V3/TGS.Server/ILogger.cs diff --git a/TGS.Server/Instance/Administration.cs b/legacy/V3/TGS.Server/Instance/Administration.cs similarity index 100% rename from TGS.Server/Instance/Administration.cs rename to legacy/V3/TGS.Server/Instance/Administration.cs diff --git a/TGS.Server/Instance/Byond.cs b/legacy/V3/TGS.Server/Instance/Byond.cs similarity index 100% rename from TGS.Server/Instance/Byond.cs rename to legacy/V3/TGS.Server/Instance/Byond.cs diff --git a/TGS.Server/Instance/Chat.cs b/legacy/V3/TGS.Server/Instance/Chat.cs similarity index 100% rename from TGS.Server/Instance/Chat.cs rename to legacy/V3/TGS.Server/Instance/Chat.cs diff --git a/TGS.Server/Instance/Compiler.cs b/legacy/V3/TGS.Server/Instance/Compiler.cs similarity index 100% rename from TGS.Server/Instance/Compiler.cs rename to legacy/V3/TGS.Server/Instance/Compiler.cs diff --git a/TGS.Server/Instance/Config.cs b/legacy/V3/TGS.Server/Instance/Config.cs similarity index 100% rename from TGS.Server/Instance/Config.cs rename to legacy/V3/TGS.Server/Instance/Config.cs diff --git a/TGS.Server/Instance/DreamDaemon.cs b/legacy/V3/TGS.Server/Instance/DreamDaemon.cs similarity index 100% rename from TGS.Server/Instance/DreamDaemon.cs rename to legacy/V3/TGS.Server/Instance/DreamDaemon.cs diff --git a/TGS.Server/Instance/Instance.cs b/legacy/V3/TGS.Server/Instance/Instance.cs similarity index 100% rename from TGS.Server/Instance/Instance.cs rename to legacy/V3/TGS.Server/Instance/Instance.cs diff --git a/TGS.Server/Instance/Interop.cs b/legacy/V3/TGS.Server/Instance/Interop.cs similarity index 100% rename from TGS.Server/Instance/Interop.cs rename to legacy/V3/TGS.Server/Instance/Interop.cs diff --git a/TGS.Server/Instance/PreactionHandler.cs b/legacy/V3/TGS.Server/Instance/PreactionHandler.cs similarity index 100% rename from TGS.Server/Instance/PreactionHandler.cs rename to legacy/V3/TGS.Server/Instance/PreactionHandler.cs diff --git a/TGS.Server/Instance/Repository.cs b/legacy/V3/TGS.Server/Instance/Repository.cs similarity index 100% rename from TGS.Server/Instance/Repository.cs rename to legacy/V3/TGS.Server/Instance/Repository.cs diff --git a/TGS.Server/InstanceConfig.cs b/legacy/V3/TGS.Server/InstanceConfig.cs similarity index 100% rename from TGS.Server/InstanceConfig.cs rename to legacy/V3/TGS.Server/InstanceConfig.cs diff --git a/TGS.Server/MessageType.cs b/legacy/V3/TGS.Server/MessageType.cs similarity index 100% rename from TGS.Server/MessageType.cs rename to legacy/V3/TGS.Server/MessageType.cs diff --git a/TGS.Server/ProcessExtension.cs b/legacy/V3/TGS.Server/ProcessExtension.cs similarity index 100% rename from TGS.Server/ProcessExtension.cs rename to legacy/V3/TGS.Server/ProcessExtension.cs diff --git a/TGS.Server/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Server/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.Server/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.Server/Properties/AssemblyInfo.cs diff --git a/TGS.Server/RepoConfig.cs b/legacy/V3/TGS.Server/RepoConfig.cs similarity index 100% rename from TGS.Server/RepoConfig.cs rename to legacy/V3/TGS.Server/RepoConfig.cs diff --git a/TGS.Server/Security/AuthenticationHeaderDecoder.cs b/legacy/V3/TGS.Server/Security/AuthenticationHeaderDecoder.cs similarity index 100% rename from TGS.Server/Security/AuthenticationHeaderDecoder.cs rename to legacy/V3/TGS.Server/Security/AuthenticationHeaderDecoder.cs diff --git a/TGS.Server/Security/RootAuthorizationManager.cs b/legacy/V3/TGS.Server/Security/RootAuthorizationManager.cs similarity index 100% rename from TGS.Server/Security/RootAuthorizationManager.cs rename to legacy/V3/TGS.Server/Security/RootAuthorizationManager.cs diff --git a/TGS.Server/Security/WindowsAuthorizationPolicy.cs b/legacy/V3/TGS.Server/Security/WindowsAuthorizationPolicy.cs similarity index 100% rename from TGS.Server/Security/WindowsAuthorizationPolicy.cs rename to legacy/V3/TGS.Server/Security/WindowsAuthorizationPolicy.cs diff --git a/TGS.Server/Server.cs b/legacy/V3/TGS.Server/Server.cs similarity index 100% rename from TGS.Server/Server.cs rename to legacy/V3/TGS.Server/Server.cs diff --git a/TGS.Server/ServerConfig.cs b/legacy/V3/TGS.Server/ServerConfig.cs similarity index 100% rename from TGS.Server/ServerConfig.cs rename to legacy/V3/TGS.Server/ServerConfig.cs diff --git a/TGS.Server/TGS.Server.csproj b/legacy/V3/TGS.Server/TGS.Server.csproj similarity index 100% rename from TGS.Server/TGS.Server.csproj rename to legacy/V3/TGS.Server/TGS.Server.csproj diff --git a/TGS.Server/packages.config b/legacy/V3/TGS.Server/packages.config similarity index 100% rename from TGS.Server/packages.config rename to legacy/V3/TGS.Server/packages.config diff --git a/TGS.Server/tgs.ico b/legacy/V3/TGS.Server/tgs.ico similarity index 100% rename from TGS.Server/tgs.ico rename to legacy/V3/TGS.Server/tgs.ico diff --git a/TGS.Tests/CommandLine/Commands/Repository/RepositoryCommandTest.cs b/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/RepositoryCommandTest.cs similarity index 100% rename from TGS.Tests/CommandLine/Commands/Repository/RepositoryCommandTest.cs rename to legacy/V3/TGS.Tests/CommandLine/Commands/Repository/RepositoryCommandTest.cs diff --git a/TGS.Tests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs b/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs similarity index 100% rename from TGS.Tests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs rename to legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs diff --git a/TGS.Tests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs b/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs similarity index 100% rename from TGS.Tests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs rename to legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs diff --git a/TGS.Tests/ControlPanel/TestInstanceSelector.cs b/legacy/V3/TGS.Tests/ControlPanel/TestInstanceSelector.cs similarity index 100% rename from TGS.Tests/ControlPanel/TestInstanceSelector.cs rename to legacy/V3/TGS.Tests/ControlPanel/TestInstanceSelector.cs diff --git a/TGS.Tests/OutputProcOverriderTest.cs b/legacy/V3/TGS.Tests/OutputProcOverriderTest.cs similarity index 100% rename from TGS.Tests/OutputProcOverriderTest.cs rename to legacy/V3/TGS.Tests/OutputProcOverriderTest.cs diff --git a/TGS.Tests/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from TGS.Tests/Properties/AssemblyInfo.cs rename to legacy/V3/TGS.Tests/Properties/AssemblyInfo.cs diff --git a/TGS.Tests/Server/TestHelpers.cs b/legacy/V3/TGS.Tests/Server/TestHelpers.cs similarity index 100% rename from TGS.Tests/Server/TestHelpers.cs rename to legacy/V3/TGS.Tests/Server/TestHelpers.cs diff --git a/TGS.Tests/ServiceInterface/TestHelpers.cs b/legacy/V3/TGS.Tests/ServiceInterface/TestHelpers.cs similarity index 100% rename from TGS.Tests/ServiceInterface/TestHelpers.cs rename to legacy/V3/TGS.Tests/ServiceInterface/TestHelpers.cs diff --git a/TGS.Tests/ServiceInterface/TestInterface.cs b/legacy/V3/TGS.Tests/ServiceInterface/TestInterface.cs similarity index 100% rename from TGS.Tests/ServiceInterface/TestInterface.cs rename to legacy/V3/TGS.Tests/ServiceInterface/TestInterface.cs diff --git a/TGS.Tests/TGS.Tests.csproj b/legacy/V3/TGS.Tests/TGS.Tests.csproj similarity index 100% rename from TGS.Tests/TGS.Tests.csproj rename to legacy/V3/TGS.Tests/TGS.Tests.csproj diff --git a/TGS.Tests/TempDirectoryRequiredTest.cs b/legacy/V3/TGS.Tests/TempDirectoryRequiredTest.cs similarity index 100% rename from TGS.Tests/TempDirectoryRequiredTest.cs rename to legacy/V3/TGS.Tests/TempDirectoryRequiredTest.cs diff --git a/TGS.Tests/packages.config b/legacy/V3/TGS.Tests/packages.config similarity index 100% rename from TGS.Tests/packages.config rename to legacy/V3/TGS.Tests/packages.config diff --git a/TGS3.json b/legacy/V3/TGS3.json similarity index 100% rename from TGS3.json rename to legacy/V3/TGS3.json diff --git a/TGStationServer3.sln b/legacy/V3/TGStationServer3.sln similarity index 100% rename from TGStationServer3.sln rename to legacy/V3/TGStationServer3.sln diff --git a/Tools/BuildDox.ps1 b/legacy/V3/Tools/BuildDox.ps1 similarity index 100% rename from Tools/BuildDox.ps1 rename to legacy/V3/Tools/BuildDox.ps1 diff --git a/Tools/Config.dm b/legacy/V3/Tools/Config.dm similarity index 100% rename from Tools/Config.dm rename to legacy/V3/Tools/Config.dm diff --git a/Tools/CoverageExclusions.runsettings b/legacy/V3/Tools/CoverageExclusions.runsettings similarity index 100% rename from Tools/CoverageExclusions.runsettings rename to legacy/V3/Tools/CoverageExclusions.runsettings diff --git a/Tools/DMAPITravisTester.dme b/legacy/V3/Tools/DMAPITravisTester.dme similarity index 100% rename from Tools/DMAPITravisTester.dme rename to legacy/V3/Tools/DMAPITravisTester.dme diff --git a/Tools/Doxyfile b/legacy/V3/Tools/Doxyfile similarity index 100% rename from Tools/Doxyfile rename to legacy/V3/Tools/Doxyfile diff --git a/Tools/PostCIBuild.ps1 b/legacy/V3/Tools/PostCIBuild.ps1 similarity index 100% rename from Tools/PostCIBuild.ps1 rename to legacy/V3/Tools/PostCIBuild.ps1 diff --git a/Tools/SignBasics.ps1 b/legacy/V3/Tools/SignBasics.ps1 similarity index 100% rename from Tools/SignBasics.ps1 rename to legacy/V3/Tools/SignBasics.ps1 diff --git a/Tools/SignMSI.ps1 b/legacy/V3/Tools/SignMSI.ps1 similarity index 100% rename from Tools/SignMSI.ps1 rename to legacy/V3/Tools/SignMSI.ps1 diff --git a/Tools/Test.dm b/legacy/V3/Tools/Test.dm similarity index 100% rename from Tools/Test.dm rename to legacy/V3/Tools/Test.dm diff --git a/Tools/UploadCoverage.ps1 b/legacy/V3/Tools/UploadCoverage.ps1 similarity index 100% rename from Tools/UploadCoverage.ps1 rename to legacy/V3/Tools/UploadCoverage.ps1 diff --git a/Tools/ViewTGS3Logs.xml b/legacy/V3/Tools/ViewTGS3Logs.xml similarity index 100% rename from Tools/ViewTGS3Logs.xml rename to legacy/V3/Tools/ViewTGS3Logs.xml diff --git a/Tools/build_byond.sh b/legacy/V3/Tools/build_byond.sh old mode 100755 new mode 100644 similarity index 100% rename from Tools/build_byond.sh rename to legacy/V3/Tools/build_byond.sh diff --git a/Tools/install_byond.sh b/legacy/V3/Tools/install_byond.sh old mode 100755 new mode 100644 similarity index 100% rename from Tools/install_byond.sh rename to legacy/V3/Tools/install_byond.sh diff --git a/Tools/tgstation13.org.pfx b/legacy/V3/Tools/tgstation13.org.pfx similarity index 100% rename from Tools/tgstation13.org.pfx rename to legacy/V3/Tools/tgstation13.org.pfx diff --git a/tgs.ico b/legacy/V3/tgs.ico similarity index 100% rename from tgs.ico rename to legacy/V3/tgs.ico From 6a94907392ce2d7a423fcce6f856b96761ecd26a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 28 Mar 2018 12:20:59 -0400 Subject: [PATCH 002/397] Add skeleton project --- .gitignore | 3 +- appveyor.yml | 39 +- build/BuildDox.ps1 | 35 + build/Doxyfile | 2479 +++++++++++++++++ build/UploadCoverage.ps1 | 10 + .../AssemblyInfo.cs | 3 + src/Tgstation.Server.Host.Console/Program.cs | 20 + .../Tgstation.Server.Host.Console.csproj | 25 + .../TestProgram.cs | 15 + ...Tgstation.Server.Host.Console.Tests.csproj | 19 + tgstation-server.sln | 48 + 11 files changed, 2672 insertions(+), 24 deletions(-) create mode 100644 build/BuildDox.ps1 create mode 100644 build/Doxyfile create mode 100644 build/UploadCoverage.ps1 create mode 100644 src/Tgstation.Server.Host.Console/AssemblyInfo.cs create mode 100644 src/Tgstation.Server.Host.Console/Program.cs create mode 100644 src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj create mode 100644 tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs create mode 100644 tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj create mode 100644 tgstation-server.sln diff --git a/.gitignore b/.gitignore index bc48bd8314..a0fa5f96e2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ artifacts/ *.suo *.userprefs *DS_Store -*.sln.ide \ No newline at end of file +*.sln.ide +/TestResults diff --git a/appveyor.yml b/appveyor.yml index d2b9fde6fa..bcadda4a23 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,37 +1,29 @@ +version: {build} pull_requests: do_not_increment_build_number: true environment: repo_token: secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK - snk_passphrase: - secure: dsRGlNewYn/JoycPaMoxLCrb3wkL31RsdU7Xwbo7TIAmClwShCVWeRPN+eGA+fMZX12MDNRBs8Fws4IxqLKQ36thQo6p2B+kPksAUJiAOR5QKJoT6nt8JB1wBMFS2kH4 -skip_branch_with_pr: true +branches: + only: + - master + - V4 +skip_tags: true image: Visual Studio 2017 -configuration: Release +configuration: + - Debug + - Release shallow_clone: true artifacts: -- path: TGS3-Server-v*.exe - name: TGS3Server -- path: MD5-SHA1-Server-v*.txt - name: MD5SHA1Server -- path: TGS3-ServerConsole-v*.zip - name: TGS3ServerConsole -- path: MD5-SHA1-ServerConsole-v*.txt - name: MD5SHA1ServerConsole -- path: TGS3-Client-v*.zip - name: TGS3Client -- path: MD5-SHA1-Client-v*.txt - name: MD5SHA1Client + - path artifacts/ServerConsole cache: - - packages -> **\packages.config + - ~\.nuget\packages -> **\*.csproj - C:\ProgramData\chocolatey\bin -> appveyor.yml - C:\ProgramData\chocolatey\lib -> appveyor.yml install: - choco install fciv doxygen.portable codecov -before_build: - - nuget restore TGStationServer3.sln build: - project: TGStationServer3.sln + project: tgstation-server.sln parallel: false verbosity: minimal publish_nuget: true @@ -43,12 +35,13 @@ after_build: test_script: - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path% - copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y - - vstest.console /logger:trx;LogFileName=results.trx "TGS.Tests\bin\Release\TGS.Tests.dll" /Enablecodecoverage /Settings:"Tools/CoverageExclusions.runsettings" /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Console.Tests\bin\Release\netcoreapp2.1\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /Settings:"Tools/CoverageExclusions.runsettings" /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) after_test: - - ps: Tools/UploadCoverage.ps1 - - ps: Tools/BuildDox.ps1 + - ps: build/UploadCoverage.ps1 + - ps: build/BuildDox.ps1 + - ps: cd src/Tgstation.Server.Host.Console && dotnet publish -o artifacts/ServerConsole -c $env:CONFIGURATION deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" diff --git a/build/BuildDox.ps1 b/build/BuildDox.ps1 new file mode 100644 index 0000000000..c9ad450975 --- /dev/null +++ b/build/BuildDox.ps1 @@ -0,0 +1,35 @@ +$bf = $Env:APPVEYOR_BUILD_FOLDER + +$doxdir = "C:\tgsdox" + +New-Item -Path $doxdir -ItemType directory + +$publish_dox = (-not (Test-Path Env:APPVEYOR_PULL_REQUEST_NUMBER)) -and ("$Env:APPVEYOR_REPO_BRANCH" -eq "master") + +if($publish_dox){ + $github_url = "github.com/$Env:APPVEYOR_REPO_NAME" + echo "Cloning https://git@$github_url..." + git clone -b gh-pages --single-branch "https://git@$github_url" "$doxdir" 2>$null + rm -r "$doxdir\*" + Add-Content "$bf\build\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/build/tgs.ico" +}else{ + Add-Content "$bf\build\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/build/tgs.ico" +} + +doxygen.exe "$bf\build\Doxyfile" + +if($publish_dox){ + cd $doxdir + git config --global push.default simple + git config user.name "tgstation-server" + git config user.email "tgstation-server@tgstation13.org" + echo '# THIS BRANCH IS AUTO GENERATED BY APPVEYOR CI' > README.md + + # Need to create a .nojekyll file to allow filenames starting with an underscore + # to be seen on the gh-pages site. Therefore creating an empty .nojekyll file. + echo "" > .nojekyll + git add --all + 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:repo_token@$github_url" 2>&1 | out-null + cd "$bf" +} diff --git a/build/Doxyfile b/build/Doxyfile new file mode 100644 index 0000000000..1f90072117 --- /dev/null +++ b/build/Doxyfile @@ -0,0 +1,2479 @@ +# Doxyfile 1.8.13 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = tgstation-server + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +# Generated in appveyor + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "The /tg/station 13 server suite" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +# Generated in appveyor + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +# Generated in appveyor + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = YES + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = NO + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +# Generated by appveyor + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +#We want the main .md +EXCLUDE = legacy/README.md packages + +# 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 +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = README.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = YES + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = . + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl + + + netstandard2.0 + + + + latest + true + + bin\Release\netstandard2.0\Tgstation.Server.Api.xml + + + + latest + + + + + + + diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj new file mode 100644 index 0000000000..c5553e1517 --- /dev/null +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -0,0 +1,34 @@ + + + + netstandard2.0 + 4.0.0.0 + false + Cyberboss + /tg/station 13 + + Client library for tgstation-server + https://tgstation.github.io/tgstation-server + https://github.com/tgstation/tgstation-server + Git + en-CA + https://raw.githubusercontent.com/tgstation/tgstation-server/master/build/tgs.ico + https://github.com/tgstation/tgstation-server/blob/master/LICENSE + + + + latest + true + + bin\Release\netstandard2.0\Tgstation.Server.Client.xml + + + + latest + + + + + + + diff --git a/src/Tgstation.Server.CommandLine/Program.cs b/src/Tgstation.Server.CommandLine/Program.cs new file mode 100644 index 0000000000..669d070b67 --- /dev/null +++ b/src/Tgstation.Server.CommandLine/Program.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace Tgstation.Server.CommandLine +{ + /// + /// Contains the entrypoint for the application + /// + public static class Program + { + /// + /// Entrypoint for the application + /// + /// The command line arguments for the application + /// A representing the running operation + public static Task Main(string[] args) => Task.CompletedTask; + } +} diff --git a/src/Tgstation.Server.CommandLine/Tgstation.Server.CommandLine.csproj b/src/Tgstation.Server.CommandLine/Tgstation.Server.CommandLine.csproj new file mode 100644 index 0000000000..1e5abf7f56 --- /dev/null +++ b/src/Tgstation.Server.CommandLine/Tgstation.Server.CommandLine.csproj @@ -0,0 +1,27 @@ + + + + Exe + netcoreapp2.0 + + + + latest + bin\Release\netcoreapp2.0\Tgstation.Server.CommandLine.xml + true + + + + + latest + + + + + + + + + + + diff --git a/src/Tgstation.Server.ControlPanel/App.config b/src/Tgstation.Server.ControlPanel/App.config new file mode 100644 index 0000000000..787dcbecc6 --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Tgstation.Server.ControlPanel/App.xaml b/src/Tgstation.Server.ControlPanel/App.xaml new file mode 100644 index 0000000000..88756359fb --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/src/Tgstation.Server.ControlPanel/App.xaml.cs b/src/Tgstation.Server.ControlPanel/App.xaml.cs new file mode 100644 index 0000000000..64dd7ac70c --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace Tgstation.Server.ControlPanel +{ + /// + /// Interaction logic for App.xaml + /// + sealed partial class App : Application + { + } +} diff --git a/src/Tgstation.Server.ControlPanel/GlobalSuppressions.cs b/src/Tgstation.Server.ControlPanel/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..abaff863e700fd537634d9306c40063c2522e58d GIT binary patch literal 1964 zcmd5-L5tI15PfIC|L~EMg4ARy(n$6%)mT%)4b8Vy=It&sD&z&kz{^jXr9 z*}FYGWl6EamcNcxp?}(6g;L@f5}pX9GsG5mjJ3dLqHK8H&?~_U@<+lGw>ZA;Q*}vCQxv45OZivy)QWNtzzT+vL z;FP*`O>XM9bTRGLB`(>q3wCQs`=~5+>#$cJn4xn2!fQ>Wg7xt1r2toh-Z{xLW!JnbD=IqH+2^Za N3~fYxt6bgU{SRd^R?GkZ literal 0 HcmV?d00001 diff --git a/src/Tgstation.Server.ControlPanel/MainWindow.xaml b/src/Tgstation.Server.ControlPanel/MainWindow.xaml new file mode 100644 index 0000000000..c2e27d3e3d --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/Tgstation.Server.ControlPanel/MainWindow.xaml.cs b/src/Tgstation.Server.ControlPanel/MainWindow.xaml.cs new file mode 100644 index 0000000000..93f05f280f --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/MainWindow.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Tgstation.Server.ControlPanel +{ + /// + /// Interaction logic for MainWindow.xaml + /// + sealed partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/src/Tgstation.Server.ControlPanel/Properties/AssemblyInfo.cs b/src/Tgstation.Server.ControlPanel/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..101764093f --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +[assembly: AssemblyTitle("Tgstation.Server.ControlPanel")] +[assembly: InternalsVisibleTo("Tgstation.Server.ControlPanel.Tests")] +[assembly: ComVisible(false)] +[assembly: CLSCompliant(true)] +[assembly: NeutralResourcesLanguage("en-CA", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + +[assembly: AssemblyVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyInformationalVersion("4.0.0.0")] diff --git a/src/Tgstation.Server.ControlPanel/Properties/Resources.Designer.cs b/src/Tgstation.Server.ControlPanel/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..6b8d38cdb3 --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tgstation.Server.ControlPanel.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Tgstation.Server.ControlPanel.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/src/Tgstation.Server.ControlPanel/Properties/Resources.resx b/src/Tgstation.Server.ControlPanel/Properties/Resources.resx new file mode 100644 index 0000000000..af7dbebbac --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Tgstation.Server.ControlPanel/Properties/Settings.Designer.cs b/src/Tgstation.Server.ControlPanel/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..5ed0707325 --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tgstation.Server.ControlPanel.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/src/Tgstation.Server.ControlPanel/Properties/Settings.settings b/src/Tgstation.Server.ControlPanel/Properties/Settings.settings new file mode 100644 index 0000000000..033d7a5e9e --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Tgstation.Server.ControlPanel/Tgstation.Server.ControlPanel.csproj b/src/Tgstation.Server.ControlPanel/Tgstation.Server.ControlPanel.csproj new file mode 100644 index 0000000000..ec1bab3bc6 --- /dev/null +++ b/src/Tgstation.Server.ControlPanel/Tgstation.Server.ControlPanel.csproj @@ -0,0 +1,107 @@ + + + + + Debug + AnyCPU + {D38BE569-04B7-4C64-BDB5-683767B26FEB} + WinExe + Tgstation.Server.ControlPanel + Tgstation.Server.ControlPanel + v4.7.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + en-CA + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + true + AllRules.ruleset + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + true + bin\Release\Tgstation.Server.ControlPanel.xml + true + AllRules.ruleset + + + Tgstation.Server.ControlPanel.App + + + + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {e0ac911f-7675-4a91-9499-d8a2e2390aad} + Tgstation.Server.Client + + + + \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index bd4bb949f7..2ce75674c3 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -5,16 +5,12 @@ namespace Tgstation.Server.Host.Console /// /// Contains the entrypoint for the application /// - static class Program + public static class Program { /// /// Entrypoint for the application /// /// A representing the running operation - public static async Task Main() - { - System.Console.WriteLine("Hello World!"); - await Task.CompletedTask.ConfigureAwait(false); - } + public static Task Main() => Task.CompletedTask; } } diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index c8d2e85c39..bbedcfffef 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs b/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs new file mode 100644 index 0000000000000000000000000000000000000000..e314a34cffba3b0b8ba16d57dbb1417779a37046 GIT binary patch literal 1660 zcmd5+O>5gg6r6J*|6$Q7fkgSBPzdDUB$V{fP>?Bvl7k{Cj#^tX`Y7bjH<@|st>nn$ z91LFV?%R3u=FRTQzh6kv@>gJm2Q2Z7b2ua%TXeP>;~FKS7N}6$YKe?vj4RG_#>}{r z+S!(o+DXu3&0oV^p?}|uLMgD1h$}*A46()|bH%tN%8Kh1qaqxVN5mX6oZ<^k@R`iM zS-y(VCC`+^YKWfTi8wtOz0FC;4i}bl&itxAwLCTX z_!9{k*M=P;RriWnTCS$tEjE#J&Kj$=dk0lIse-@uALGW5s-)U~a$FKAXWbq3km8Gt8+2x<{}{*AbIyLt zXU;sJF1yXVw|wg41N-d$JJd})>X@l!#EQA)ICWd2y#Jneo%O-f$4%L6)PR&w@|0D=}u+b*S*UoI4RGlww8PTjtrHiT%X<)-S3WF z60W{=kMstKwdMtPvymDDpSjhMGM{n?eYX}3COCRM`jz$UzcyFBLKKfO#Hk(6~!?%r>KT{Ll=d-GoU+Z7ZZa~)Vjd>ePU*So^u zE3>NdXr!pa9oh6`m7AjShkRQxZtT>c^lp{C=e@Inql3==epSq&8?Lw4yIZ_p0qV0L AY5)KL literal 0 HcmV?d00001 diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs new file mode 100644 index 0000000000..0e1f417d3d --- /dev/null +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -0,0 +1,16 @@ +namespace Tgstation.Server.Host.Service +{ + /// + /// Contains the entrypoint for the application + /// + static class Program + { + /// + /// Entrypoint for the application + /// + /// A representing the running operation + static void Main() + { + } + } +} diff --git a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..295c7cb2a0 --- /dev/null +++ b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Tgstation.Server.Host.Service")] +[assembly: ComVisible(false)] +[assembly: Guid("29927416-3b78-49a7-a560-5ccaa638b6b4")] +[assembly: InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")] + +[assembly: AssemblyVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyInformationalVersion("4.0.0.0")] diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj new file mode 100644 index 0000000000..4072e3c73b --- /dev/null +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + {29927416-3B78-49A7-A560-5CCAA638B6B4} + WinExe + Tgstation.Server.Host.Service + Tgstation.Server.Host.Service + v4.7.1 + 512 + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + true + AllRules.ruleset + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + true + bin\Release\Tgstation.Server.Host.Service.xml + true + AllRules.ruleset + + + Tgstation.Server.Host.Service.Program + + + + + + + + + False + Microsoft .NET Framework 4.7.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + {2b69ad6d-2b5a-4023-8ead-0bd1b18e028a} + Tgstation.Server.Host + + + + \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj new file mode 100644 index 0000000000..6082d6d99c --- /dev/null +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -0,0 +1,27 @@ + + + + netstandard2.0 + + + + latest + true + + bin\Release\netstandard2.0\Tgstation.Server.Host.xml + + + + latest + + + + + + + + + + + + diff --git a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj new file mode 100644 index 0000000000..103f4472b1 --- /dev/null +++ b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + + false + latest + + + + + + + + + + + + + diff --git a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj new file mode 100644 index 0000000000..8061f3b7e8 --- /dev/null +++ b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + + false + latest + + + + + + + + + + + + + diff --git a/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj b/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj new file mode 100644 index 0000000000..46bbd02d4a --- /dev/null +++ b/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + + false + latest + + + + + + + + + + + + + diff --git a/tests/Tgstation.Server.ControlPanel.Tests/Properties/AssemblyInfo.cs b/tests/Tgstation.Server.ControlPanel.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8a27ad97fc --- /dev/null +++ b/tests/Tgstation.Server.ControlPanel.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Tgstation.Server.ControlPanel.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tgstation.Server.ControlPanel.Tests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("93ca946d-50de-4ad4-bada-d0353f0affcc")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/Tgstation.Server.ControlPanel.Tests/Tgstation.Server.ControlPanel.Tests.csproj b/tests/Tgstation.Server.ControlPanel.Tests/Tgstation.Server.ControlPanel.Tests.csproj new file mode 100644 index 0000000000..d3d8ba781d --- /dev/null +++ b/tests/Tgstation.Server.ControlPanel.Tests/Tgstation.Server.ControlPanel.Tests.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC} + Library + Properties + Tgstation.Server.ControlPanel.Tests + Tgstation.Server.ControlPanel.Tests + v4.7.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + + ..\..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + {d38be569-04b7-4c64-bdb5-683767b26feb} + Tgstation.Server.ControlPanel + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/tests/Tgstation.Server.ControlPanel.Tests/packages.config b/tests/Tgstation.Server.ControlPanel.Tests/packages.config new file mode 100644 index 0000000000..8819694ccc --- /dev/null +++ b/tests/Tgstation.Server.ControlPanel.Tests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj index 65432ec2af..1234f45518 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj +++ b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj @@ -4,6 +4,7 @@ netcoreapp2.0 false + latest diff --git a/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs b/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..e7c6fc8a51 --- /dev/null +++ b/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Tgstation.Server.Host.Service.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tgstation.Server.Host.Service.Tests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("f0cd3f14-e9c0-4477-99f7-f1e22cb8d85e")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj new file mode 100644 index 0000000000..3741d9e073 --- /dev/null +++ b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E} + Library + Properties + Tgstation.Server.Host.Service.Tests + Tgstation.Server.Host.Service.Tests + v4.7.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + latest + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + latest + + + + ..\..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + {29927416-3b78-49a7-a560-5ccaa638b6b4} + Tgstation.Server.Host.Service + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Service.Tests/packages.config b/tests/Tgstation.Server.Host.Service.Tests/packages.config new file mode 100644 index 0000000000..8819694ccc --- /dev/null +++ b/tests/Tgstation.Server.Host.Service.Tests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj new file mode 100644 index 0000000000..4f0518d744 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + + false + latest + + + + + + + + + + + + + diff --git a/tgstation-server.sln b/tgstation-server.sln index 9924c0b2db..25c76f7da8 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -21,9 +21,34 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6 ProjectSection(SolutionItems) = preProject build\BuildDox.ps1 = build\BuildDox.ps1 build\Doxyfile = build\Doxyfile + build\tgs.ico = build\tgs.ico build\UploadCoverage.ps1 = build\UploadCoverage.ps1 EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host", "src\Tgstation.Server.Host\Tgstation.Server.Host.csproj", "{2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Api", "src\Tgstation.Server.Api\Tgstation.Server.Api.csproj", "{8B4A208D-A48A-4A5D-8B94-E2661138865D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Service", "src\Tgstation.Server.Host.Service\Tgstation.Server.Host.Service.csproj", "{29927416-3B78-49A7-A560-5CCAA638B6B4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.CommandLine", "src\Tgstation.Server.CommandLine\Tgstation.Server.CommandLine.csproj", "{657C3624-3534-4A0C-B4E7-196FC830F547}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.ControlPanel", "src\Tgstation.Server.ControlPanel\Tgstation.Server.ControlPanel.csproj", "{D38BE569-04B7-4C64-BDB5-683767B26FEB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Client", "src\Tgstation.Server.Client\Tgstation.Server.Client.csproj", "{E0AC911F-7675-4A91-9499-D8A2E2390AAD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.ControlPanel.Tests", "tests\Tgstation.Server.ControlPanel.Tests\Tgstation.Server.ControlPanel.Tests.csproj", "{93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Service.Tests", "tests\Tgstation.Server.Host.Service.Tests\Tgstation.Server.Host.Service.Tests.csproj", "{F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Api.Tests", "tests\Tgstation.Server.Api.Tests\Tgstation.Server.Api.Tests.csproj", "{BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Client.Tests", "tests\Tgstation.Server.Client.Tests\Tgstation.Server.Client.Tests.csproj", "{E5301AF1-4F74-4982-BF24-95F23CC5D5B2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.CommandLine.Tests", "tests\Tgstation.Server.CommandLine.Tests\Tgstation.Server.CommandLine.Tests.csproj", "{90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Tests", "tests\Tgstation.Server.Host.Tests\Tgstation.Server.Host.Tests.csproj", "{A3362FF6-550F-480F-859E-8EC1EB6EAB31}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,6 +63,54 @@ Global {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Debug|Any CPU.Build.0 = Debug|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Release|Any CPU.Build.0 = Release|Any CPU + {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Release|Any CPU.Build.0 = Release|Any CPU + {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Release|Any CPU.Build.0 = Release|Any CPU + {29927416-3B78-49A7-A560-5CCAA638B6B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29927416-3B78-49A7-A560-5CCAA638B6B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29927416-3B78-49A7-A560-5CCAA638B6B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29927416-3B78-49A7-A560-5CCAA638B6B4}.Release|Any CPU.Build.0 = Release|Any CPU + {657C3624-3534-4A0C-B4E7-196FC830F547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {657C3624-3534-4A0C-B4E7-196FC830F547}.Debug|Any CPU.Build.0 = Debug|Any CPU + {657C3624-3534-4A0C-B4E7-196FC830F547}.Release|Any CPU.ActiveCfg = Release|Any CPU + {657C3624-3534-4A0C-B4E7-196FC830F547}.Release|Any CPU.Build.0 = Release|Any CPU + {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Release|Any CPU.Build.0 = Release|Any CPU + {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Release|Any CPU.Build.0 = Release|Any CPU + {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Release|Any CPU.Build.0 = Release|Any CPU + {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}.Release|Any CPU.Build.0 = Release|Any CPU + {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Release|Any CPU.Build.0 = Release|Any CPU + {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Release|Any CPU.Build.0 = Release|Any CPU + {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Release|Any CPU.Build.0 = Release|Any CPU + {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 329d309eb1e91bc2b6f9c18eecbca2be9fca0fd1 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 28 Mar 2018 16:36:55 -0400 Subject: [PATCH 004/397] Add docker support --- build/Dockerfile | 37 +++++++++++++++++++ build/docker-compose/.dockerignore | 11 ++++++ build/docker-compose/docker-compose.dcproj | 15 ++++++++ .../docker-compose.override.yml | 1 + build/docker-compose/docker-compose.yml | 8 ++++ tgstation-server.sln | 16 +++++--- 6 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 build/Dockerfile create mode 100644 build/docker-compose/.dockerignore create mode 100644 build/docker-compose/docker-compose.dcproj create mode 100644 build/docker-compose/docker-compose.override.yml create mode 100644 build/docker-compose/docker-compose.yml diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000000..d66d3aab02 --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,37 @@ +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /src + +COPY tgstation-server.sln ./ + +COPY src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj src/Tgstation.Server.Host.Console/ +COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/ +COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/ + +COPY tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj tests/Tgstation.Server.Api.Console.Tests/ +COPY tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj tests/Tgstation.Server.Host.Console.Tests/ +COPY tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj tests/Tgstation.Server.Host.Console.Tests/ + +RUN dotnet restore -nowarn:msb3202,nu1503 + +COPY . . + +WORKDIR /src/src/Tgstation.Server.Host.Console +RUN dotnet build -c Release -o /app + +WORKDIR /src/tests/Tgstation.Server.Api.Tests +RUN dotnet test -c Release + +WORKDIR /src/tests/Tgstation.Server.Host.Tests +RUN dotnet test -c Release + +WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests +RUN dotnet test -c Release + +FROM build AS publish +RUN dotnet publish -c Release -o /app + +FROM microsoft/dotnet:2.1-runtime +EXPOSE 80 +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Tgstation.Server.Host.Console.dll"] diff --git a/build/docker-compose/.dockerignore b/build/docker-compose/.dockerignore new file mode 100644 index 0000000000..4f1c2ff0b4 --- /dev/null +++ b/build/docker-compose/.dockerignore @@ -0,0 +1,11 @@ +*/.dockerignore +*/docker-compose.yml +*/docker-compose.*.yml +.env +.git +.gitignore +.vs +.vscode +legacy +*/bin +*/obj diff --git a/build/docker-compose/docker-compose.dcproj b/build/docker-compose/docker-compose.dcproj new file mode 100644 index 0000000000..996cb0b18b --- /dev/null +++ b/build/docker-compose/docker-compose.dcproj @@ -0,0 +1,15 @@ + + + + 2.0 + Linux + db2a5ee6-af5c-410c-a36c-ea378fe9cbfe + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/build/docker-compose/docker-compose.override.yml b/build/docker-compose/docker-compose.override.yml new file mode 100644 index 0000000000..6bdf591f00 --- /dev/null +++ b/build/docker-compose/docker-compose.override.yml @@ -0,0 +1 @@ +version: '3' diff --git a/build/docker-compose/docker-compose.yml b/build/docker-compose/docker-compose.yml new file mode 100644 index 0000000000..579b20745f --- /dev/null +++ b/build/docker-compose/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3' + +services: + tgstation.server: + image: tgstationserver + build: + context: ../.. + dockerfile: build/Dockerfile diff --git a/tgstation-server.sln b/tgstation-server.sln index 25c76f7da8..4b42b2e615 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -13,9 +13,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Console", "src\Tgstation.Server.Host.Console\Tgstation.Server.Host.Console.csproj", "{A09C947F-4B9B-4AEE-AB50-47055EA05F18}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Console", "src\Tgstation.Server.Host.Console\Tgstation.Server.Host.Console.csproj", "{A09C947F-4B9B-4AEE-AB50-47055EA05F18}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Console.Tests", "tests\Tgstation.Server.Host.Console.Tests\Tgstation.Server.Host.Console.Tests.csproj", "{D36B99C4-E771-42D6-A95F-1102B3E236DF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Console.Tests", "tests\Tgstation.Server.Host.Console.Tests\Tgstation.Server.Host.Console.Tests.csproj", "{D36B99C4-E771-42D6-A95F-1102B3E236DF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6-3E2C-46D4-872D-D528F77D6973}" ProjectSection(SolutionItems) = preProject @@ -25,17 +25,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6 build\UploadCoverage.ps1 = build\UploadCoverage.ps1 EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host", "src\Tgstation.Server.Host\Tgstation.Server.Host.csproj", "{2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host", "src\Tgstation.Server.Host\Tgstation.Server.Host.csproj", "{2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Api", "src\Tgstation.Server.Api\Tgstation.Server.Api.csproj", "{8B4A208D-A48A-4A5D-8B94-E2661138865D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Service", "src\Tgstation.Server.Host.Service\Tgstation.Server.Host.Service.csproj", "{29927416-3B78-49A7-A560-5CCAA638B6B4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.CommandLine", "src\Tgstation.Server.CommandLine\Tgstation.Server.CommandLine.csproj", "{657C3624-3534-4A0C-B4E7-196FC830F547}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.CommandLine", "src\Tgstation.Server.CommandLine\Tgstation.Server.CommandLine.csproj", "{657C3624-3534-4A0C-B4E7-196FC830F547}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.ControlPanel", "src\Tgstation.Server.ControlPanel\Tgstation.Server.ControlPanel.csproj", "{D38BE569-04B7-4C64-BDB5-683767B26FEB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Client", "src\Tgstation.Server.Client\Tgstation.Server.Client.csproj", "{E0AC911F-7675-4A91-9499-D8A2E2390AAD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Client", "src\Tgstation.Server.Client\Tgstation.Server.Client.csproj", "{E0AC911F-7675-4A91-9499-D8A2E2390AAD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.ControlPanel.Tests", "tests\Tgstation.Server.ControlPanel.Tests\Tgstation.Server.ControlPanel.Tests.csproj", "{93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}" EndProject @@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.CommandLin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Tests", "tests\Tgstation.Server.Host.Tests\Tgstation.Server.Host.Tests.csproj", "{A3362FF6-550F-480F-859E-8EC1EB6EAB31}" EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "build\docker-compose\docker-compose.dcproj", "{DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -111,6 +113,10 @@ Global {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Debug|Any CPU.Build.0 = Debug|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.Build.0 = Release|Any CPU + {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d2e9833c5e5a75ab31ae5190775c1fa3d51ff386 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 11:54:59 -0400 Subject: [PATCH 005/397] Fix docker build --- .dockerignore | 9 +++++++++ build/Dockerfile | 6 +++--- build/docker-compose/.dockerignore | 11 ----------- build/docker-compose/docker-compose.override.yml | 1 - .../docker-compose.dcproj => docker-compose.dcproj | 3 --- .../docker-compose.yml => docker-compose.yml | 2 +- src/Tgstation.Server.CommandLine/Program.cs | 3 +-- src/Tgstation.Server.ControlPanel/MainWindow.xaml.cs | 3 +++ src/Tgstation.Server.Host.Console/Program.cs | 6 +++++- src/Tgstation.Server.Host.Service/Program.cs | 1 - tgstation-server.sln | 3 ++- 11 files changed, 24 insertions(+), 24 deletions(-) create mode 100644 .dockerignore delete mode 100644 build/docker-compose/.dockerignore delete mode 100644 build/docker-compose/docker-compose.override.yml rename build/docker-compose/docker-compose.dcproj => docker-compose.dcproj (76%) rename build/docker-compose/docker-compose.yml => docker-compose.yml (84%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..c3189160e5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.dockerignore +docker-compose.yml +.git +.gitignore +.vs +.vscode +legacy +*/bin +*/obj diff --git a/build/Dockerfile b/build/Dockerfile index d66d3aab02..1c99478ef7 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -7,11 +7,11 @@ COPY src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj src/ COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/ COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/ -COPY tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj tests/Tgstation.Server.Api.Console.Tests/ -COPY tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj tests/Tgstation.Server.Host.Console.Tests/ +COPY tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj tests/Tgstation.Server.Api.Tests/ +COPY tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj tests/Tgstation.Server.Host.Tests/ COPY tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj tests/Tgstation.Server.Host.Console.Tests/ -RUN dotnet restore -nowarn:msb3202,nu1503 +RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=false COPY . . diff --git a/build/docker-compose/.dockerignore b/build/docker-compose/.dockerignore deleted file mode 100644 index 4f1c2ff0b4..0000000000 --- a/build/docker-compose/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -*/.dockerignore -*/docker-compose.yml -*/docker-compose.*.yml -.env -.git -.gitignore -.vs -.vscode -legacy -*/bin -*/obj diff --git a/build/docker-compose/docker-compose.override.yml b/build/docker-compose/docker-compose.override.yml deleted file mode 100644 index 6bdf591f00..0000000000 --- a/build/docker-compose/docker-compose.override.yml +++ /dev/null @@ -1 +0,0 @@ -version: '3' diff --git a/build/docker-compose/docker-compose.dcproj b/docker-compose.dcproj similarity index 76% rename from build/docker-compose/docker-compose.dcproj rename to docker-compose.dcproj index 996cb0b18b..99c91b2938 100644 --- a/build/docker-compose/docker-compose.dcproj +++ b/docker-compose.dcproj @@ -6,9 +6,6 @@ db2a5ee6-af5c-410c-a36c-ea378fe9cbfe - - docker-compose.yml - diff --git a/build/docker-compose/docker-compose.yml b/docker-compose.yml similarity index 84% rename from build/docker-compose/docker-compose.yml rename to docker-compose.yml index 579b20745f..77f97d69bb 100644 --- a/build/docker-compose/docker-compose.yml +++ b/docker-compose.yml @@ -4,5 +4,5 @@ services: tgstation.server: image: tgstationserver build: - context: ../.. + context: . dockerfile: build/Dockerfile diff --git a/src/Tgstation.Server.CommandLine/Program.cs b/src/Tgstation.Server.CommandLine/Program.cs index 669d070b67..63d8f4fbdc 100644 --- a/src/Tgstation.Server.CommandLine/Program.cs +++ b/src/Tgstation.Server.CommandLine/Program.cs @@ -10,8 +10,7 @@ namespace Tgstation.Server.CommandLine /// /// Entrypoint for the application /// - /// The command line arguments for the application /// A representing the running operation - public static Task Main(string[] args) => Task.CompletedTask; + public static Task Main() => Task.CompletedTask; } } diff --git a/src/Tgstation.Server.ControlPanel/MainWindow.xaml.cs b/src/Tgstation.Server.ControlPanel/MainWindow.xaml.cs index 93f05f280f..09ab37f920 100644 --- a/src/Tgstation.Server.ControlPanel/MainWindow.xaml.cs +++ b/src/Tgstation.Server.ControlPanel/MainWindow.xaml.cs @@ -20,6 +20,9 @@ namespace Tgstation.Server.ControlPanel /// sealed partial class MainWindow : Window { + /// + /// Construct a + /// public MainWindow() { InitializeComponent(); diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index 2ce75674c3..0c8a1dc557 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -11,6 +11,10 @@ namespace Tgstation.Server.Host.Console /// Entrypoint for the application /// /// A representing the running operation - public static Task Main() => Task.CompletedTask; + public static Task Main() + { + System.Console.WriteLine("Hello world!"); + return Task.CompletedTask; + } } } diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 0e1f417d3d..880b0a08b6 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -8,7 +8,6 @@ /// /// Entrypoint for the application /// - /// A representing the running operation static void Main() { } diff --git a/tgstation-server.sln b/tgstation-server.sln index 4b42b2e615..dda595392f 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -20,6 +20,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6-3E2C-46D4-872D-D528F77D6973}" ProjectSection(SolutionItems) = preProject build\BuildDox.ps1 = build\BuildDox.ps1 + build\Dockerfile = build\Dockerfile build\Doxyfile = build\Doxyfile build\tgs.ico = build\tgs.ico build\UploadCoverage.ps1 = build\UploadCoverage.ps1 @@ -49,7 +50,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.CommandLin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Tests", "tests\Tgstation.Server.Host.Tests\Tgstation.Server.Host.Tests.csproj", "{A3362FF6-550F-480F-859E-8EC1EB6EAB31}" EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "build\docker-compose\docker-compose.dcproj", "{DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}" +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 73ef71561ac0d6d6c3736dc20ac6d2024dc19db6 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:01:09 -0400 Subject: [PATCH 006/397] Fix CI ymls --- .travis.yml | 30 ++++++++++-------------------- appveyor.yml | 2 +- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index df970b29e7..fd3c934d18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,17 @@ language: csharp sudo: false +dotnet: 2.1 +mono: none -env: - global: - - BYOND_MAJOR="511" - - BYOND_MINOR="1385" - - DMEName="Tools/DMAPITravisTester.dme" - matrix: - - BUILD_CLIENT=true - - BUILD_CLIENT=false - -cache: - directories: - - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} - -addons: - apt: - packages: - - libc6-i386 - - libstdc++6:i386 +matrix: + include: + - os: linux + env: CONFIGURATION="Debug" + - os: linux + env: CONFIGURATION="Release" install: - - if [ "$BUILD_CLIENT" = true ]; then nuget restore; else ./Tools/install_byond.sh; fi + - dotnet restore tgstation-server.sln script: - - if [ "$BUILD_CLIENT" = true ]; then msbuild /p:Configuration=Release-Client TGStationServer3.sln; else ./Tools/build_byond.sh; fi + - dotnet test -c $CONFIGURATION tgstation-server.sln diff --git a/appveyor.yml b/appveyor.yml index 1c3cc40930..cf1f0e7ce0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: {build} +version: '{build}' pull_requests: do_not_increment_build_number: true environment: From 688153078fe5098330fcc4e8ffc305a9947e6363 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:01:48 -0400 Subject: [PATCH 007/397] Fix appveyor again --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index cf1f0e7ce0..a4d3acfb7f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,7 +15,7 @@ configuration: - Release shallow_clone: true artifacts: - - path artifacts/ServerConsole + - path: artifacts/ServerConsole cache: - ~\.nuget\packages -> **\*.csproj - C:\ProgramData\chocolatey\bin -> appveyor.yml From eaab40d72bc25509d6ff85b60c0d816f5db8a7c0 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:02:32 -0400 Subject: [PATCH 008/397] Fix travis dotnet version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fd3c934d18..d41625464a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: csharp sudo: false -dotnet: 2.1 +dotnet: 2.1.101 mono: none matrix: From 3547c540f3826dba15988ea6fddcd54454edbd1e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:05:19 -0400 Subject: [PATCH 009/397] Remove appveyor afterbuild for now --- appveyor.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a4d3acfb7f..b662553a60 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,11 +28,6 @@ build: parallel: false verbosity: minimal publish_nuget: true -after_build: - - ps: Tools/PostCIBuild.ps1 - - ps: if($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[TGSDeploy\]"){ if($env:APPVEYOR_REPO_BRANCH -match "master"){ $env:TGSDeploy = "Do it." }} - - ps: if($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[NugetDeploy\]"){ if($env:APPVEYOR_REPO_BRANCH -match "master"){ $env:NugetDeploy = "Do it." }} - - ps: $env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/TGS.Server/bin/Release/TGS.Server.dll").FileVersion test_script: - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path% - copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y From 769453f6151e02924e5cd9792efbe9ec350284a1 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:24:22 -0400 Subject: [PATCH 010/397] Fix test directories --- appveyor.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b662553a60..6d95cd9675 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,11 +31,11 @@ build: test_script: - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path% - copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Api.Tests\bin\%CONFIGURATION%\netcoreapp2.1\Tgstation.Server.Api.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Client.Tests\bin\%CONFIGURATION%\netcoreapp2.1\Tgstation.Server.Client.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.CommandLine.Tests\bin\%CONFIGURATION%\netcoreapp2.1\Tgstation.Server.CommandLine.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.1\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.1\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Api.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Api.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Client.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Client.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.CommandLine.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.CommandLine.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.ControlPanel.Tests\bin\%CONFIGURATION%\Tgstation.Server.ControlPanel.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' From 024bd4e725fdc362e3f16d16d9061a7efb70ec43 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:48:29 -0400 Subject: [PATCH 011/397] Makes travis on run on the host console --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d41625464a..534ebabd14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ matrix: env: CONFIGURATION="Release" install: - - dotnet restore tgstation-server.sln + - cd src/Tgstation.Server.Host.Console && dotnet restore script: - - dotnet test -c $CONFIGURATION tgstation-server.sln + - cd src/Tgstation.Server.Host.Console && dotnet test -c $CONFIGURATION tgstation-server.sln From f3693f3e96bf3332499df84676c6f420ecf347b5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:57:33 -0400 Subject: [PATCH 012/397] src -> tests --- appveyor.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6d95cd9675..e71e22b4c9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,13 +31,13 @@ build: test_script: - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path% - copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Api.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Api.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Client.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Client.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.CommandLine.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.CommandLine.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - vstest.console /logger:trx;LogFileName=results.trx "src\Tgstation.Server.ControlPanel.Tests\bin\%CONFIGURATION%\Tgstation.Server.ControlPanel.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Api.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Api.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Client.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Client.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.CommandLine.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.CommandLine.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.ControlPanel.Tests\bin\%CONFIGURATION%\Tgstation.Server.ControlPanel.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) after_test: From 46a1113c78b9a1e132c6ebe5796a5d0b117d4677 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:58:53 -0400 Subject: [PATCH 013/397] Fix travis commands --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 534ebabd14..819441fcc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,4 +14,4 @@ install: - cd src/Tgstation.Server.Host.Console && dotnet restore script: - - cd src/Tgstation.Server.Host.Console && dotnet test -c $CONFIGURATION tgstation-server.sln + - dotnet test -c $CONFIGURATION From d99939f19d119d783f0c0213a5e180ac75cb2db2 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 1 Apr 2018 12:59:29 -0400 Subject: [PATCH 014/397] Removes travis --- .travis.yml | 17 ----------------- tgstation-server.sln | 1 - 2 files changed, 18 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 819441fcc0..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: csharp -sudo: false -dotnet: 2.1.101 -mono: none - -matrix: - include: - - os: linux - env: CONFIGURATION="Debug" - - os: linux - env: CONFIGURATION="Release" - -install: - - cd src/Tgstation.Server.Host.Console && dotnet restore - -script: - - dotnet test -c $CONFIGURATION diff --git a/tgstation-server.sln b/tgstation-server.sln index dda595392f..c2d4b4770c 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -7,7 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .codecov.yml = .codecov.yml .gitignore = .gitignore - .travis.yml = .travis.yml appveyor.yml = appveyor.yml LICENSE = LICENSE README.md = README.md From e36b41923a1af390637d4c0c3ca1ddcfba4978a2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 10:04:52 -0400 Subject: [PATCH 015/397] Final appveyor fix hopefully --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e71e22b4c9..5b119512ee 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -43,7 +43,7 @@ test_script: after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 - - ps: cd src/Tgstation.Server.Host.Console && dotnet publish -o artifacts/ServerConsole -c $env:CONFIGURATION + - ps: cd src/Tgstation.Server.Host.Console -and dotnet publish -o artifacts/ServerConsole -c $env:CONFIGURATION deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" From 1a02379f3f865858503ee8a089afa45801296ad6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 11:42:59 -0400 Subject: [PATCH 016/397] Skeletonize the client some --- src/Tgstation.Server.Api/Models/Instance.cs | 28 +++++++++++ src/Tgstation.Server.Api/Models/Token.cs | 13 +++++ src/Tgstation.Server.Api/Models/TokenInfo.cs | 25 ++++++++++ .../Components/IInstanceClient.cs | 16 ++++++ .../Components/IInstanceManagerClient.cs | 40 +++++++++++++++ .../Components/ITokenClient.cs | 50 +++++++++++++++++++ src/Tgstation.Server.Client/IClient.cs | 27 ++++++++++ src/Tgstation.Server.Client/IServerClient.cs | 36 +++++++++++++ .../IServerClientFactory.cs | 31 ++++++++++++ .../Rights/InstanceManagerRights.cs | 48 ++++++++++++++++++ .../Rights/InstanceRights.cs | 12 +++++ .../Rights/ServerRights.cs | 25 ++++++++++ .../Rights/TokenRights.cs | 32 ++++++++++++ .../ServerClientFactory.cs | 15 ++++++ .../TestServerClientFactory.cs | 27 ++++++++++ 15 files changed, 425 insertions(+) create mode 100644 src/Tgstation.Server.Api/Models/Instance.cs create mode 100644 src/Tgstation.Server.Api/Models/Token.cs create mode 100644 src/Tgstation.Server.Api/Models/TokenInfo.cs create mode 100644 src/Tgstation.Server.Client/Components/IInstanceClient.cs create mode 100644 src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs create mode 100644 src/Tgstation.Server.Client/Components/ITokenClient.cs create mode 100644 src/Tgstation.Server.Client/IClient.cs create mode 100644 src/Tgstation.Server.Client/IServerClient.cs create mode 100644 src/Tgstation.Server.Client/IServerClientFactory.cs create mode 100644 src/Tgstation.Server.Client/Rights/InstanceManagerRights.cs create mode 100644 src/Tgstation.Server.Client/Rights/InstanceRights.cs create mode 100644 src/Tgstation.Server.Client/Rights/ServerRights.cs create mode 100644 src/Tgstation.Server.Client/Rights/TokenRights.cs create mode 100644 src/Tgstation.Server.Client/ServerClientFactory.cs create mode 100644 tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs new file mode 100644 index 0000000000..42fbc3c710 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -0,0 +1,28 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// Metadata about a server instance + /// + public sealed class Instance + { + /// + /// The id of the + /// + public long Id { get; set; } + + /// + /// The name of the + /// + public string Name { get; set; } + + /// + /// The path to where the is located + /// + public string Path { get; set; } + + /// + /// If the is online + /// + public bool Online { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs new file mode 100644 index 0000000000..c8f34cc70b --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -0,0 +1,13 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// An access token + /// + public sealed class Token : TokenInfo + { + /// + /// The token + /// + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/TokenInfo.cs b/src/Tgstation.Server.Api/Models/TokenInfo.cs new file mode 100644 index 0000000000..e458065a21 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/TokenInfo.cs @@ -0,0 +1,25 @@ +using System.Net; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Information about a + /// + public class TokenInfo + { + /// + /// The id of the + /// + public long Id { get; } + + /// + /// The user agent that created the + /// + public string ClientUserAgent { get; set; } + + /// + /// The the was originally issued to + /// + public IPAddress IssuedTo { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs new file mode 100644 index 0000000000..29d6d50a1c --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -0,0 +1,16 @@ +using Tgstation.Server.Api.Models; +using Tgstation.Server.Client.Rights; + +namespace Tgstation.Server.Client +{ + /// + /// for server instances + /// + public interface IInstanceClient : IClient + { + /// + /// The of the + /// + Instance Metadata { get; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs new file mode 100644 index 0000000000..64cfd74760 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Client.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// Manager for s + /// + public interface IInstanceManagerClient : IClient + { + /// + /// Get all s for s that the can view + /// + /// A resulting in a of s + Task> GetInstanceClients(); + + /// + /// Create an + /// + /// The to create. will be ignored + /// A resulting in for the created + Task CreateInstance(Instance instance); + + /// + /// Relocates, renamed, and/or on/offlines an + /// + /// The to update + /// A representing the running operation + Task UpdateInstance(Instance instance); + + /// + /// Deletes an + /// + /// The to delete + /// A representing the running operation + Task DeleteInstance(Instance instance); + } +} diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs new file mode 100644 index 0000000000..45e30b9791 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/ITokenClient.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Client.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// for managing s + /// + public interface ITokenClient: IClient + { + /// + /// Generates a new token for the 's + /// + /// A resulting in a new for the + Task Generate(); + + /// + /// Gets all active s for the + /// + /// A resulting a of active s for the + Task> GetClientInfos(); + + /// + /// Gets all active s for all users + /// + /// A resulting a of active s for the keyed by username + Task> GetAllInfos(); + + /// + /// Invalidate the specified active designated by + /// + /// The of the to invalidate + /// A representing the running operation + Task Invalidate(long tokenId); + + /// + /// Invalidate all active s for a + /// + /// A representing the running operation + Task InvalidateAllClient(); + + /// + /// Invalidate all active s + /// + /// A representing the running operation + Task InvalidateAll(); + } +} diff --git a/src/Tgstation.Server.Client/IClient.cs b/src/Tgstation.Server.Client/IClient.cs new file mode 100644 index 0000000000..3b34351384 --- /dev/null +++ b/src/Tgstation.Server.Client/IClient.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; + +namespace Tgstation.Server.Client +{ + /// + /// Basic client + /// + /// The for the + public interface IClient + { + /// + /// The connection timeout in milliseconds + /// + int Timeout { get; set; } + + /// + /// The requery rate for job updates in milliseconds + /// + int RequeryRate { get; set; } + + /// + /// Get the for the + /// + /// A resulting in the for the + Task Rights(); + } +} diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs new file mode 100644 index 0000000000..c745c793c5 --- /dev/null +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Tgstation.Server.Client.Components; +using Tgstation.Server.Client.Rights; + +namespace Tgstation.Server.Client +{ + /// + /// for communicating with a server + /// + public interface IServerClient : IDisposable, IClient + { + /// + /// The of the + /// + Version Version { get; } + + /// + /// Access the + /// + ITokenClient Token { get; } + + /// + /// Access the + /// + IInstanceManagerClient Instance { get; } + + /// + /// The of the connected server + /// + /// A resulting in the of the connected server + /// Note that if the differs from the 's API functionality will most likely be compromised + Task GetServerVersion(); + } +} diff --git a/src/Tgstation.Server.Client/IServerClientFactory.cs b/src/Tgstation.Server.Client/IServerClientFactory.cs new file mode 100644 index 0000000000..710f32aa76 --- /dev/null +++ b/src/Tgstation.Server.Client/IServerClientFactory.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; + +namespace Tgstation.Server.Client +{ + /// + /// Factory for creating s + /// + public interface IServerClientFactory + { + /// + /// Create a + /// + /// The URL to access tgstation-server at + /// The username to for the + /// The password for the + /// The initial timeout for the connection + /// A resulting in a new + /// If the and/or is invalid + Task CreateServerClient(string hostname, string username, string password, int timeout = 10000); + + /// + /// Create a + /// + /// The URL to access tgstation-server at + /// The to access the API with + /// The initial timeout for the connection + /// A resulting in a new + /// If the invalid + Task CreateServerClient(string hostname, string token, int timeout = 10000); + } +} diff --git a/src/Tgstation.Server.Client/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Client/Rights/InstanceManagerRights.cs new file mode 100644 index 0000000000..7414d61db8 --- /dev/null +++ b/src/Tgstation.Server.Client/Rights/InstanceManagerRights.cs @@ -0,0 +1,48 @@ +using System; + +namespace Tgstation.Server.Client.Rights +{ + /// + /// Rights for + /// + [Flags] + public enum InstanceManagerRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User can view s which they have any rights for + /// + Read = 1, + /// + /// User can create s + /// + Create = 2, + /// + /// User can rename s they can view + /// + Rename = 4, + /// + /// User can relocate s they can view + /// + Relocate = 8, + /// + /// User can online s they can view + /// + Online = 16, + /// + /// User can offline s they can view + /// + Offline = 32, + /// + /// User can delete s they can view + /// + Delete = 64, + /// + /// User can view all s + /// + List = 128 + } +} diff --git a/src/Tgstation.Server.Client/Rights/InstanceRights.cs b/src/Tgstation.Server.Client/Rights/InstanceRights.cs new file mode 100644 index 0000000000..20b2998f72 --- /dev/null +++ b/src/Tgstation.Server.Client/Rights/InstanceRights.cs @@ -0,0 +1,12 @@ +using System; + +namespace Tgstation.Server.Client.Rights +{ + /// + /// Rights for an + /// + [Flags] + public enum InstanceRights + { + } +} diff --git a/src/Tgstation.Server.Client/Rights/ServerRights.cs b/src/Tgstation.Server.Client/Rights/ServerRights.cs new file mode 100644 index 0000000000..d875941010 --- /dev/null +++ b/src/Tgstation.Server.Client/Rights/ServerRights.cs @@ -0,0 +1,25 @@ +using System; + +namespace Tgstation.Server.Client.Rights +{ + /// + /// Rights a has for a server. If a method isn't listed below + /// + [Flags] + public enum ServerRights + { + /// + /// Client has no rights + /// + None = 0, + /// + /// Allow access to + /// + /// This implies access to any APIs not otherwise listed here + Version = 1, + /// + /// Allow access to + /// + Tokens = 2, + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Rights/TokenRights.cs b/src/Tgstation.Server.Client/Rights/TokenRights.cs new file mode 100644 index 0000000000..70bddf5441 --- /dev/null +++ b/src/Tgstation.Server.Client/Rights/TokenRights.cs @@ -0,0 +1,32 @@ +using System; + +namespace Tgstation.Server.Client.Rights +{ + /// + /// Rights for + /// + [Flags] + public enum TokenRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User can create s + /// + Create = 1, + /// + /// User can delete s + /// + Delete = 2, + /// + /// User can list all their s + /// + List = 4, + /// + /// User can perform all the actions they can for themselves on behalf of other users except + /// + Admin = 8, + } +} diff --git a/src/Tgstation.Server.Client/ServerClientFactory.cs b/src/Tgstation.Server.Client/ServerClientFactory.cs new file mode 100644 index 0000000000..d54f2211b0 --- /dev/null +++ b/src/Tgstation.Server.Client/ServerClientFactory.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; + +namespace Tgstation.Server.Client +{ + /// + public sealed class ServerClientFactory : IServerClientFactory + { + /// + public Task CreateServerClient(string hostname, string username, string password, int timeout = 10000) => throw new NotImplementedException(); + + /// + public Task CreateServerClient(string hostname, string token, int timeout = 10000) => throw new NotImplementedException(); + } +} diff --git a/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs b/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs new file mode 100644 index 0000000000..6b5f203e6b --- /dev/null +++ b/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs @@ -0,0 +1,27 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading.Tasks; + +namespace Tgstation.Server.Client.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestServerClientFactory + { + [TestMethod] + public async Task TestUserConstruction() + { + var factory = new ServerClientFactory(); + await Assert.ThrowsExceptionAsync(() => factory.CreateServerClient(null, null, null)).ConfigureAwait(false); + } + + [TestMethod] + public async Task TestTokenConstruction() + { + var factory = new ServerClientFactory(); + await Assert.ThrowsExceptionAsync(() => factory.CreateServerClient(null, null)).ConfigureAwait(false); + } + } +} From 3a31ed5d78ee9f52c9e15e80140e66388286f3fc Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 11:45:55 -0400 Subject: [PATCH 017/397] Try and fix appveyor yet again --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5b119512ee..85d6d24a54 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -43,7 +43,7 @@ test_script: after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 - - ps: cd src/Tgstation.Server.Host.Console -and dotnet publish -o artifacts/ServerConsole -c $env:CONFIGURATION + - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o artifacts/ServerConsole -c $env:CONFIGURATION deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" From be8fe6d26e0b968f0c40ef0c6606d1218403c4bd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 12:11:42 -0400 Subject: [PATCH 018/397] Byond client --- .../Models/ByondStatus.cs | 35 ++++++++++++++++ .../Components/IByondClient.cs | 33 +++++++++++++++ .../Components/IInstanceClient.cs | 7 +++- .../Rights/ByondRights.cs | 40 +++++++++++++++++++ .../Rights/InstanceRights.cs | 10 ++++- .../Rights/ServerRights.cs | 2 +- 6 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/ByondStatus.cs create mode 100644 src/Tgstation.Server.Client/Components/IByondClient.cs create mode 100644 src/Tgstation.Server.Client/Rights/ByondRights.cs diff --git a/src/Tgstation.Server.Api/Models/ByondStatus.cs b/src/Tgstation.Server.Api/Models/ByondStatus.cs new file mode 100644 index 0000000000..d722f1c4e7 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/ByondStatus.cs @@ -0,0 +1,35 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// The status of a BYOND update job + /// +#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names + public enum ByondStatus +#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names + { + /// + /// No update in progress + /// + Idle, + /// + /// Preparing to update + /// + Starting, + /// + /// Revision is downloading + /// + Downloading, + /// + /// Revision is deflating + /// + Staging, + /// + /// Revision is ready and waiting for DreamDaemon reboot + /// + Staged, + /// + /// Revision is being applied + /// + Updating, + } +} diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs new file mode 100644 index 0000000000..76291f4347 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Client.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// For managing the BYOND installation + /// + public interface IByondClient : IClient + { + /// + /// Gets the current status of any BYOND updates + /// + /// A resulting in the updater + Task CurrentStatus(); + + /// + /// Updates the installed BYOND + /// + /// The to update to. Only considers the and numbers + /// A representing the running operation + Task UpdateToVersion(Version version); + + /// + /// Get the currently installed BYOND + /// + /// Read the staged instead if applicable + /// A resulting in the installed BYOND version or if none is installed + Task GetVersion(bool staged); + } +} diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 29d6d50a1c..6dd435c045 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -1,7 +1,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Client.Rights; -namespace Tgstation.Server.Client +namespace Tgstation.Server.Client.Components { /// /// for server instances @@ -12,5 +12,10 @@ namespace Tgstation.Server.Client /// The of the /// Instance Metadata { get; } + + /// + /// Access the + /// + IByondClient Byond { get; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Rights/ByondRights.cs b/src/Tgstation.Server.Client/Rights/ByondRights.cs new file mode 100644 index 0000000000..3c17bc4439 --- /dev/null +++ b/src/Tgstation.Server.Client/Rights/ByondRights.cs @@ -0,0 +1,40 @@ +using System; + +namespace Tgstation.Server.Client.Rights +{ + /// + /// Rights for + /// + [Flags] + public enum ByondRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User may check the installed BYOND version + /// + ReadInstalled = 1, + /// + /// User may check the staged BYOND version + /// + ReadStaged = 2, + /// + /// User may install any BYOND version if none is installed + /// + Install = 4, + /// + /// User may upgrade the installed BYOND version + /// + Upgrade = 8, + /// + /// User may downgrade the installed BYOND version + /// + Downgrade = 16, + /// + /// User may cancel any pending installation job + /// + Cancel = 32 + } +} diff --git a/src/Tgstation.Server.Client/Rights/InstanceRights.cs b/src/Tgstation.Server.Client/Rights/InstanceRights.cs index 20b2998f72..0613badfc2 100644 --- a/src/Tgstation.Server.Client/Rights/InstanceRights.cs +++ b/src/Tgstation.Server.Client/Rights/InstanceRights.cs @@ -3,10 +3,18 @@ namespace Tgstation.Server.Client.Rights { /// - /// Rights for an + /// Rights for an /// [Flags] public enum InstanceRights { + /// + /// User has no rights + /// + None = 0, + /// + /// Allow access to + /// + Byond = 1, } } diff --git a/src/Tgstation.Server.Client/Rights/ServerRights.cs b/src/Tgstation.Server.Client/Rights/ServerRights.cs index d875941010..640f44dc3f 100644 --- a/src/Tgstation.Server.Client/Rights/ServerRights.cs +++ b/src/Tgstation.Server.Client/Rights/ServerRights.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Client.Rights public enum ServerRights { /// - /// Client has no rights + /// User has no rights /// None = 0, /// From dbc5ca7e58becb43fddcfc7d14d163aecbed7d1e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 12:16:23 -0400 Subject: [PATCH 019/397] Adds CancellationTokens to all operations --- .../Components/IByondClient.cs | 10 +++++++--- .../Components/IInstanceManagerClient.cs | 13 +++++++++---- .../Components/ITokenClient.cs | 19 +++++++++++++------ src/Tgstation.Server.Client/IClient.cs | 6 ++++-- src/Tgstation.Server.Client/IServerClient.cs | 3 ++- .../IServerClientFactory.cs | 11 +++++++---- .../ServerClientFactory.cs | 5 +++-- .../TestServerClientFactory.cs | 4 ++-- 8 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs index 76291f4347..2c9ca74652 100644 --- a/src/Tgstation.Server.Client/Components/IByondClient.cs +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Client.Rights; @@ -13,21 +14,24 @@ namespace Tgstation.Server.Client.Components /// /// Gets the current status of any BYOND updates /// + /// The for the operation /// A resulting in the updater - Task CurrentStatus(); + Task CurrentStatus(CancellationToken cancellationToken); /// /// Updates the installed BYOND /// /// The to update to. Only considers the and numbers + /// The for the operation /// A representing the running operation - Task UpdateToVersion(Version version); + Task UpdateToVersion(Version version, CancellationToken cancellationToken); /// /// Get the currently installed BYOND /// /// Read the staged instead if applicable + /// The for the operation /// A resulting in the installed BYOND version or if none is installed - Task GetVersion(bool staged); + Task GetVersion(bool staged, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs index 64cfd74760..f615eaa389 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Client.Rights; @@ -13,28 +14,32 @@ namespace Tgstation.Server.Client.Components /// /// Get all s for s that the can view /// + /// The for the operation /// A resulting in a of s - Task> GetInstanceClients(); + Task> GetInstanceClients(CancellationToken cancellationToken); /// /// Create an /// /// The to create. will be ignored + /// The for the operation /// A resulting in for the created - Task CreateInstance(Instance instance); + Task CreateInstance(Instance instance, CancellationToken cancellationToken); /// /// Relocates, renamed, and/or on/offlines an /// /// The to update + /// The for the operation /// A representing the running operation - Task UpdateInstance(Instance instance); + Task UpdateInstance(Instance instance, CancellationToken cancellationToken); /// /// Deletes an /// /// The to delete + /// The for the operation /// A representing the running operation - Task DeleteInstance(Instance instance); + Task DeleteInstance(Instance instance, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs index 45e30b9791..7365fedf11 100644 --- a/src/Tgstation.Server.Client/Components/ITokenClient.cs +++ b/src/Tgstation.Server.Client/Components/ITokenClient.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Client.Rights; @@ -13,38 +14,44 @@ namespace Tgstation.Server.Client.Components /// /// Generates a new token for the 's /// + /// The for the operation /// A resulting in a new for the - Task Generate(); + Task Generate(CancellationToken cancellationToken); /// /// Gets all active s for the /// + /// The for the operation /// A resulting a of active s for the - Task> GetClientInfos(); + Task> GetClientInfos(CancellationToken cancellationToken); /// /// Gets all active s for all users /// + /// The for the operation /// A resulting a of active s for the keyed by username - Task> GetAllInfos(); + Task> GetAllInfos(CancellationToken cancellationToken); /// /// Invalidate the specified active designated by /// /// The of the to invalidate + /// The for the operation /// A representing the running operation - Task Invalidate(long tokenId); + Task Invalidate(long tokenId, CancellationToken cancellationToken); /// /// Invalidate all active s for a /// + /// The for the operation /// A representing the running operation - Task InvalidateAllClient(); + Task InvalidateAllClient(CancellationToken cancellationToken); /// /// Invalidate all active s /// + /// The for the operation /// A representing the running operation - Task InvalidateAll(); + Task InvalidateAll(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/IClient.cs b/src/Tgstation.Server.Client/IClient.cs index 3b34351384..2091805d10 100644 --- a/src/Tgstation.Server.Client/IClient.cs +++ b/src/Tgstation.Server.Client/IClient.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Client { @@ -21,7 +22,8 @@ namespace Tgstation.Server.Client /// /// Get the for the /// + /// The for the operation /// A resulting in the for the - Task Rights(); + Task Rights(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index c745c793c5..6e1baa11d4 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Client.Components; using Tgstation.Server.Client.Rights; @@ -31,6 +32,6 @@ namespace Tgstation.Server.Client /// /// A resulting in the of the connected server /// Note that if the differs from the 's API functionality will most likely be compromised - Task GetServerVersion(); + Task GetServerVersion(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/IServerClientFactory.cs b/src/Tgstation.Server.Client/IServerClientFactory.cs index 710f32aa76..fb79446eb0 100644 --- a/src/Tgstation.Server.Client/IServerClientFactory.cs +++ b/src/Tgstation.Server.Client/IServerClientFactory.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Client { @@ -13,10 +14,11 @@ namespace Tgstation.Server.Client /// The URL to access tgstation-server at /// The username to for the /// The password for the - /// The initial timeout for the connection + /// The initial timeout for the connection in milliseconds + /// The for the operation /// A resulting in a new /// If the and/or is invalid - Task CreateServerClient(string hostname, string username, string password, int timeout = 10000); + Task CreateServerClient(string hostname, string username, string password, int timeout, CancellationToken cancellationToken); /// /// Create a @@ -24,8 +26,9 @@ namespace Tgstation.Server.Client /// The URL to access tgstation-server at /// The to access the API with /// The initial timeout for the connection + /// The for the operation /// A resulting in a new /// If the invalid - Task CreateServerClient(string hostname, string token, int timeout = 10000); + Task CreateServerClient(string hostname, string token, int timeout, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/ServerClientFactory.cs b/src/Tgstation.Server.Client/ServerClientFactory.cs index d54f2211b0..1d4af06329 100644 --- a/src/Tgstation.Server.Client/ServerClientFactory.cs +++ b/src/Tgstation.Server.Client/ServerClientFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Client @@ -7,9 +8,9 @@ namespace Tgstation.Server.Client public sealed class ServerClientFactory : IServerClientFactory { /// - public Task CreateServerClient(string hostname, string username, string password, int timeout = 10000) => throw new NotImplementedException(); + public Task CreateServerClient(string hostname, string username, string password, int timeout, CancellationToken cancellationToken) => throw new NotImplementedException(); /// - public Task CreateServerClient(string hostname, string token, int timeout = 10000) => throw new NotImplementedException(); + public Task CreateServerClient(string hostname, string token, int timeout, CancellationToken cancellationToken) => throw new NotImplementedException(); } } diff --git a/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs b/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs index 6b5f203e6b..981ce7295e 100644 --- a/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs +++ b/tests/Tgstation.Server.Client.Tests/TestServerClientFactory.cs @@ -14,14 +14,14 @@ namespace Tgstation.Server.Client.Tests public async Task TestUserConstruction() { var factory = new ServerClientFactory(); - await Assert.ThrowsExceptionAsync(() => factory.CreateServerClient(null, null, null)).ConfigureAwait(false); + await Assert.ThrowsExceptionAsync(() => factory.CreateServerClient(null, null, null, default, default)).ConfigureAwait(false); } [TestMethod] public async Task TestTokenConstruction() { var factory = new ServerClientFactory(); - await Assert.ThrowsExceptionAsync(() => factory.CreateServerClient(null, null)).ConfigureAwait(false); + await Assert.ThrowsExceptionAsync(() => factory.CreateServerClient(null, null, default, default)).ConfigureAwait(false); } } } From 7dd3cdb3a3330d833e59d414a92e83d190fcbafe Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 12:31:07 -0400 Subject: [PATCH 020/397] Move rights to the API --- src/Tgstation.Server.Api/Models/Byond.cs | 20 ++++++++ .../Models/ByondStatus.cs | 6 ++- .../Rights/ByondRights.cs | 12 +++-- .../Rights/InstanceManagerRights.cs | 48 +++++++++++++++++++ .../Rights/InstanceRights.cs | 6 +-- .../Rights/ServerRights.cs | 24 ++++++++++ .../Rights/TokenRights.cs | 10 ++-- .../Components/IByondClient.cs | 9 ++-- .../Components/IInstanceClient.cs | 2 +- .../Components/IInstanceManagerClient.cs | 2 +- .../Components/ITokenClient.cs | 2 +- src/Tgstation.Server.Client/IClient.cs | 2 +- src/Tgstation.Server.Client/IServerClient.cs | 3 +- .../Rights/InstanceManagerRights.cs | 48 ------------------- .../Rights/ServerRights.cs | 25 ---------- 15 files changed, 123 insertions(+), 96 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Byond.cs rename src/{Tgstation.Server.Client => Tgstation.Server.Api}/Rights/ByondRights.cs (71%) create mode 100644 src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs rename src/{Tgstation.Server.Client => Tgstation.Server.Api}/Rights/InstanceRights.cs (56%) create mode 100644 src/Tgstation.Server.Api/Rights/ServerRights.cs rename src/{Tgstation.Server.Client => Tgstation.Server.Api}/Rights/TokenRights.cs (63%) delete mode 100644 src/Tgstation.Server.Client/Rights/InstanceManagerRights.cs delete mode 100644 src/Tgstation.Server.Client/Rights/ServerRights.cs diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs new file mode 100644 index 0000000000..d0de46ce47 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -0,0 +1,20 @@ +using System; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a BYOND installation + /// + public sealed class Byond + { + /// + /// The for the installation + /// + public ByondStatus ByondStatus { get; set; } + + /// + /// The of the installation. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers + /// + public Version Version { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/ByondStatus.cs b/src/Tgstation.Server.Api/Models/ByondStatus.cs index d722f1c4e7..638a91e475 100644 --- a/src/Tgstation.Server.Api/Models/ByondStatus.cs +++ b/src/Tgstation.Server.Api/Models/ByondStatus.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Api.Models { /// - /// The status of a BYOND update job + /// The status of a update job /// #pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names public enum ByondStatus @@ -31,5 +31,9 @@ /// Revision is being applied /// Updating, + /// + /// User does not have permission to view the + /// + Hidden, } } diff --git a/src/Tgstation.Server.Client/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs similarity index 71% rename from src/Tgstation.Server.Client/Rights/ByondRights.cs rename to src/Tgstation.Server.Api/Rights/ByondRights.cs index 3c17bc4439..0b96b9783a 100644 --- a/src/Tgstation.Server.Client/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -1,9 +1,9 @@ using System; -namespace Tgstation.Server.Client.Rights +namespace Tgstation.Server.Api.Rights { /// - /// Rights for + /// Rights for /// [Flags] public enum ByondRights @@ -33,8 +33,12 @@ namespace Tgstation.Server.Client.Rights /// Downgrade = 16, /// - /// User may cancel any pending installation job + /// User may cancel a pending installation job /// - Cancel = 32 + Cancel = 32, + /// + /// User may read the of the installation job + /// + ReadStatus = 64, } } diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs new file mode 100644 index 0000000000..31a7eea0d3 --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -0,0 +1,48 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights for managing s + /// + [Flags] + public enum InstanceManagerRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User can view s which they have any rights for + /// + Read = 1, + /// + /// User can create s + /// + Create = 2, + /// + /// User can rename s they can view + /// + Rename = 4, + /// + /// User can relocate s they can view + /// + Relocate = 8, + /// + /// User can online s they can view + /// + Online = 16, + /// + /// User can offline s they can view + /// + Offline = 32, + /// + /// User can delete s they can view + /// + Delete = 64, + /// + /// User can view all s + /// + List = 128 + } +} diff --git a/src/Tgstation.Server.Client/Rights/InstanceRights.cs b/src/Tgstation.Server.Api/Rights/InstanceRights.cs similarity index 56% rename from src/Tgstation.Server.Client/Rights/InstanceRights.cs rename to src/Tgstation.Server.Api/Rights/InstanceRights.cs index 0613badfc2..31dfa21825 100644 --- a/src/Tgstation.Server.Client/Rights/InstanceRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceRights.cs @@ -1,9 +1,9 @@ using System; -namespace Tgstation.Server.Client.Rights +namespace Tgstation.Server.Api.Rights { /// - /// Rights for an + /// Rights for an /// [Flags] public enum InstanceRights @@ -13,7 +13,7 @@ namespace Tgstation.Server.Client.Rights /// None = 0, /// - /// Allow access to + /// Allow access to /// Byond = 1, } diff --git a/src/Tgstation.Server.Api/Rights/ServerRights.cs b/src/Tgstation.Server.Api/Rights/ServerRights.cs new file mode 100644 index 0000000000..60f6e98172 --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/ServerRights.cs @@ -0,0 +1,24 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights a user has for an entire server + /// + [Flags] + public enum ServerRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// Allow access to the of the server + /// + Version = 1, + /// + /// Allow access to s + /// + Tokens = 2, + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Rights/TokenRights.cs b/src/Tgstation.Server.Api/Rights/TokenRights.cs similarity index 63% rename from src/Tgstation.Server.Client/Rights/TokenRights.cs rename to src/Tgstation.Server.Api/Rights/TokenRights.cs index 70bddf5441..ba958fd380 100644 --- a/src/Tgstation.Server.Client/Rights/TokenRights.cs +++ b/src/Tgstation.Server.Api/Rights/TokenRights.cs @@ -1,9 +1,9 @@ using System; -namespace Tgstation.Server.Client.Rights +namespace Tgstation.Server.Api.Rights { /// - /// Rights for + /// Rights for s /// [Flags] public enum TokenRights @@ -13,15 +13,15 @@ namespace Tgstation.Server.Client.Rights /// None = 0, /// - /// User can create s + /// User can create s /// Create = 1, /// - /// User can delete s + /// User can delete s /// Delete = 2, /// - /// User can list all their s + /// User can list all their s /// List = 4, /// diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs index 2c9ca74652..942fcf1fc7 100644 --- a/src/Tgstation.Server.Client/Components/IByondClient.cs +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Client.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { @@ -16,15 +16,16 @@ namespace Tgstation.Server.Client.Components /// /// The for the operation /// A resulting in the updater - Task CurrentStatus(CancellationToken cancellationToken); + Task Read(CancellationToken cancellationToken); /// /// Updates the installed BYOND /// - /// The to update to. Only considers the and numbers + /// The to update + /// Optional taking a to run when it changes /// The for the operation /// A representing the running operation - Task UpdateToVersion(Version version, CancellationToken cancellationToken); + Task Update(Byond byond, Action progressCallback, CancellationToken cancellationToken); /// /// Get the currently installed BYOND diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 6dd435c045..03b2a06e70 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -1,5 +1,5 @@ using Tgstation.Server.Api.Models; -using Tgstation.Server.Client.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { diff --git a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs index f615eaa389..d8eb5493c6 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Client.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs index 7365fedf11..155975f528 100644 --- a/src/Tgstation.Server.Client/Components/ITokenClient.cs +++ b/src/Tgstation.Server.Client/Components/ITokenClient.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Client.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { diff --git a/src/Tgstation.Server.Client/IClient.cs b/src/Tgstation.Server.Client/IClient.cs index 2091805d10..b83a7248d6 100644 --- a/src/Tgstation.Server.Client/IClient.cs +++ b/src/Tgstation.Server.Client/IClient.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Client /// /// Basic client /// - /// The for the + /// The for the public interface IClient { /// diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index 6e1baa11d4..72f602066d 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Client.Components; -using Tgstation.Server.Client.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client { diff --git a/src/Tgstation.Server.Client/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Client/Rights/InstanceManagerRights.cs deleted file mode 100644 index 7414d61db8..0000000000 --- a/src/Tgstation.Server.Client/Rights/InstanceManagerRights.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace Tgstation.Server.Client.Rights -{ - /// - /// Rights for - /// - [Flags] - public enum InstanceManagerRights - { - /// - /// User has no rights - /// - None = 0, - /// - /// User can view s which they have any rights for - /// - Read = 1, - /// - /// User can create s - /// - Create = 2, - /// - /// User can rename s they can view - /// - Rename = 4, - /// - /// User can relocate s they can view - /// - Relocate = 8, - /// - /// User can online s they can view - /// - Online = 16, - /// - /// User can offline s they can view - /// - Offline = 32, - /// - /// User can delete s they can view - /// - Delete = 64, - /// - /// User can view all s - /// - List = 128 - } -} diff --git a/src/Tgstation.Server.Client/Rights/ServerRights.cs b/src/Tgstation.Server.Client/Rights/ServerRights.cs deleted file mode 100644 index 640f44dc3f..0000000000 --- a/src/Tgstation.Server.Client/Rights/ServerRights.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Tgstation.Server.Client.Rights -{ - /// - /// Rights a has for a server. If a method isn't listed below - /// - [Flags] - public enum ServerRights - { - /// - /// User has no rights - /// - None = 0, - /// - /// Allow access to - /// - /// This implies access to any APIs not otherwise listed here - Version = 1, - /// - /// Allow access to - /// - Tokens = 2, - } -} \ No newline at end of file From a56d27dbe0ea6f6bbd5cb24a20416eddd2d7e924 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 12:33:19 -0400 Subject: [PATCH 021/397] Fix appveyor again --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 85d6d24a54..f51af44eb6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -43,7 +43,7 @@ test_script: after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 - - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o artifacts/ServerConsole -c $env:CONFIGURATION + - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o artifacts/ServerConsole -c %CONFIGURATION% deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" From 30aa01579ac52ba2679e94aa69e2224e26454976 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 12:34:07 -0400 Subject: [PATCH 022/397] Add legacy folder to exclude --- build/Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Doxyfile b/build/Doxyfile index 1f90072117..b4ec282ad2 100644 --- a/build/Doxyfile +++ b/build/Doxyfile @@ -865,7 +865,7 @@ RECURSIVE = YES # run. #We want the main .md -EXCLUDE = legacy/README.md packages +EXCLUDE = legacy packages # 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 From 14ac2e91efc5acb835f2ddb29896481b54b0c9f3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 2 Apr 2018 12:45:17 -0400 Subject: [PATCH 023/397] Questionable behaviour refactor --- .../Rights/ServerRights.cs | 24 ----------------- .../Components/IByondClient.cs | 9 +------ .../Components/IInstanceClient.cs | 9 ++----- .../Components/IInstanceManagerClient.cs | 15 +++-------- .../Components/ITokenClient.cs | 13 +++------- src/Tgstation.Server.Client/IClient.cs | 26 +++++++++---------- src/Tgstation.Server.Client/IServerClient.cs | 19 ++++++++++---- 7 files changed, 36 insertions(+), 79 deletions(-) delete mode 100644 src/Tgstation.Server.Api/Rights/ServerRights.cs diff --git a/src/Tgstation.Server.Api/Rights/ServerRights.cs b/src/Tgstation.Server.Api/Rights/ServerRights.cs deleted file mode 100644 index 60f6e98172..0000000000 --- a/src/Tgstation.Server.Api/Rights/ServerRights.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Tgstation.Server.Api.Rights -{ - /// - /// Rights a user has for an entire server - /// - [Flags] - public enum ServerRights - { - /// - /// User has no rights - /// - None = 0, - /// - /// Allow access to the of the server - /// - Version = 1, - /// - /// Allow access to s - /// - Tokens = 2, - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs index 942fcf1fc7..a8415c3570 100644 --- a/src/Tgstation.Server.Client/Components/IByondClient.cs +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -9,15 +9,8 @@ namespace Tgstation.Server.Client.Components /// /// For managing the BYOND installation /// - public interface IByondClient : IClient + public interface IByondClient : IClient { - /// - /// Gets the current status of any BYOND updates - /// - /// The for the operation - /// A resulting in the updater - Task Read(CancellationToken cancellationToken); - /// /// Updates the installed BYOND /// diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 03b2a06e70..5710aadda9 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -4,15 +4,10 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { /// - /// for server instances + /// for server instances /// - public interface IInstanceClient : IClient + public interface IInstanceClient : IClient { - /// - /// The of the - /// - Instance Metadata { get; } - /// /// Access the /// diff --git a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs index d8eb5493c6..f4197a8bfe 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs @@ -9,22 +9,15 @@ namespace Tgstation.Server.Client.Components /// /// Manager for s /// - public interface IInstanceManagerClient : IClient + public interface IInstanceManagerClient : IClient> { - /// - /// Get all s for s that the can view - /// - /// The for the operation - /// A resulting in a of s - Task> GetInstanceClients(CancellationToken cancellationToken); - /// /// Create an /// /// The to create. will be ignored /// The for the operation /// A resulting in for the created - Task CreateInstance(Instance instance, CancellationToken cancellationToken); + Task Create(Instance instance, CancellationToken cancellationToken); /// /// Relocates, renamed, and/or on/offlines an @@ -32,7 +25,7 @@ namespace Tgstation.Server.Client.Components /// The to update /// The for the operation /// A representing the running operation - Task UpdateInstance(Instance instance, CancellationToken cancellationToken); + Task Update(Instance instance, CancellationToken cancellationToken); /// /// Deletes an @@ -40,6 +33,6 @@ namespace Tgstation.Server.Client.Components /// The to delete /// The for the operation /// A representing the running operation - Task DeleteInstance(Instance instance, CancellationToken cancellationToken); + Task Delete(Instance instance, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs index 155975f528..c93a853944 100644 --- a/src/Tgstation.Server.Client/Components/ITokenClient.cs +++ b/src/Tgstation.Server.Client/Components/ITokenClient.cs @@ -9,27 +9,20 @@ namespace Tgstation.Server.Client.Components /// /// for managing s /// - public interface ITokenClient: IClient + public interface ITokenClient: IClient { - /// - /// Generates a new token for the 's - /// - /// The for the operation - /// A resulting in a new for the - Task Generate(CancellationToken cancellationToken); - /// /// Gets all active s for the /// /// The for the operation - /// A resulting a of active s for the + /// A resulting a of active s for the Task> GetClientInfos(CancellationToken cancellationToken); /// /// Gets all active s for all users /// /// The for the operation - /// A resulting a of active s for the keyed by username + /// A resulting a of active s for the keyed by username Task> GetAllInfos(CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Client/IClient.cs b/src/Tgstation.Server.Client/IClient.cs index b83a7248d6..8fd0598243 100644 --- a/src/Tgstation.Server.Client/IClient.cs +++ b/src/Tgstation.Server.Client/IClient.cs @@ -6,24 +6,22 @@ namespace Tgstation.Server.Client /// /// Basic client /// - /// The for the - public interface IClient + /// The for the + /// Which of the the represents + public interface IClient { /// - /// The connection timeout in milliseconds - /// - int Timeout { get; set; } - - /// - /// The requery rate for job updates in milliseconds - /// - int RequeryRate { get; set; } - - /// - /// Get the for the + /// Get the for the /// /// The for the operation - /// A resulting in the for the + /// A resulting in the for the Task Rights(CancellationToken cancellationToken); + + /// + /// Read the of the + /// + /// The for the operation + /// The of the + Task Read(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index 72f602066d..406ae13b26 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -2,29 +2,38 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Client.Components; -using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client { /// - /// for communicating with a server + /// Main client for communicating with a server /// - public interface IServerClient : IDisposable, IClient + public interface IServerClient : IDisposable { /// /// The of the /// Version Version { get; } + /// + /// The connection timeout in milliseconds + /// + int Timeout { get; set; } + + /// + /// The requery rate for job updates in milliseconds + /// + int RequeryRate { get; set; } + /// /// Access the /// - ITokenClient Token { get; } + ITokenClient Tokens { get; } /// /// Access the /// - IInstanceManagerClient Instance { get; } + IInstanceManagerClient Instances { get; } /// /// The of the connected server From d1458c6e20b06f6f1e9fa696991a46a708ac1005 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 2 Apr 2018 15:53:12 -0400 Subject: [PATCH 024/397] More shit --- build/Dockerfile1709 | 37 ++++++++++ src/Tgstation.Server.Api/Models/Byond.cs | 7 +- src/Tgstation.Server.Api/Models/Repository.cs | 67 +++++++++++++++++++ .../Rights/RepositoryRights.cs | 60 +++++++++++++++++ .../Components/IByondClient.cs | 19 +++--- .../Components/IInstanceClient.cs | 20 +++++- .../Components/IInstanceManagerClient.cs | 2 +- .../Components/IRepositoryClient.cs | 30 +++++++++ .../Components/ITokenClient.cs | 2 +- src/Tgstation.Server.Client/IClient.cs | 27 -------- src/Tgstation.Server.Client/IRightsClient.cs | 19 ++++++ src/Tgstation.Server.Client/IServerClient.cs | 9 ++- tgstation-server.sln | 1 + 13 files changed, 255 insertions(+), 45 deletions(-) create mode 100644 build/Dockerfile1709 create mode 100644 src/Tgstation.Server.Api/Models/Repository.cs create mode 100644 src/Tgstation.Server.Api/Rights/RepositoryRights.cs create mode 100644 src/Tgstation.Server.Client/Components/IRepositoryClient.cs delete mode 100644 src/Tgstation.Server.Client/IClient.cs create mode 100644 src/Tgstation.Server.Client/IRightsClient.cs diff --git a/build/Dockerfile1709 b/build/Dockerfile1709 new file mode 100644 index 0000000000..2d3b7ddc8d --- /dev/null +++ b/build/Dockerfile1709 @@ -0,0 +1,37 @@ +FROM microsoft/dotnet:2.1-sdk-nanoserver-1709 AS build +WORKDIR /src + +COPY tgstation-server.sln ./ + +COPY src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj src/Tgstation.Server.Host.Console/ +COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/ +COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/ + +COPY tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj tests/Tgstation.Server.Api.Tests/ +COPY tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj tests/Tgstation.Server.Host.Tests/ +COPY tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj tests/Tgstation.Server.Host.Console.Tests/ + +RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=false + +COPY . . + +WORKDIR /src/src/Tgstation.Server.Host.Console +RUN dotnet build -c Release -o /app + +WORKDIR /src/tests/Tgstation.Server.Api.Tests +RUN dotnet test -c Release + +WORKDIR /src/tests/Tgstation.Server.Host.Tests +RUN dotnet test -c Release + +WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests +RUN dotnet test -c Release + +FROM build AS publish +RUN dotnet publish -c Release -o /app + +FROM microsoft/dotnet:2.1-runtime-nanoserver-1709 +EXPOSE 80 +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Tgstation.Server.Host.Console.dll"] diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index d0de46ce47..be91273f66 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -13,8 +13,13 @@ namespace Tgstation.Server.Api.Models public ByondStatus ByondStatus { get; set; } /// - /// The of the installation. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers + /// The of the installation. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers /// public Version Version { get; set; } + + /// + /// The of the that's staged for installation + /// + public Version StagedVersion { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs new file mode 100644 index 0000000000..17591cf066 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a git repository + /// + public sealed class Repository + { + /// + /// The origin URL. If , the does not exist + /// + public string Origin { get; set; } + + /// + /// The commit HEAD points to + /// + public string Sha { get; set; } + + /// + /// The branch HEAD points to + /// + public string Branch { get; set; } + + /// + /// The name of the committer + /// + public string CommitterName { get; set; } + + /// + /// The e-mail of the committer + /// + public string CommitterEmail { get; set; } + + /// + /// Associated list of tag name -> sha for repository compiles + /// + public IReadOnlyDictionary Backups { get; set; } + + /// + /// Associated list of GitHub pull request number -> sha for merged pull requests. Adding a value to this list will merge the latest commit of the pull request numbered by the key + /// +#pragma warning disable CA2227 // Collection properties should be read only + public Dictionary PullRequests { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only + + /// + /// The username to access the git repository with + /// + public string AccessUser { get; set; } + + /// + /// The token/password to access the git repository with + /// + public string AccessToken { get; set; } + + /// + /// If commits created from testmerges are pushed to the remote + /// + public bool PushTestMergeCommits { get; set; } + + /// + /// How often the automatically updates in minutes + /// + public int? AutoUpdateInterval { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs new file mode 100644 index 0000000000..4574b2ae6b --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -0,0 +1,60 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights for a + /// + [Flags] + public enum RepositoryRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User may create the if it does not exist + /// + Create = 1, + /// + /// User may delete the + /// + Delete = 2, + /// + /// User may directly set the sha the 's HEAD points to + /// + SetSha = 8, + /// + /// User may fetch and merge GitHub pull requests + /// + MergePullRequest = 16, + /// + /// User may use the update feature + /// + Update = 32, + /// + /// User may change and + /// + ChangeCommitter = 64, + /// + /// User may change + /// + ChangeTestMergeCommits = 128, + /// + /// User may change + /// + ChangeAutoUpdate = 256, + /// + /// User may read and change and + /// + ChangeCredentials = 512, + /// + /// User may reset the 's HEAD to the origin reference of whatever branch it is tracking + /// + Reset = 1024, + /// + /// User may read all fields in the with the exception of + /// + Read = 2048 + } +} diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs index a8415c3570..f7e064e0b2 100644 --- a/src/Tgstation.Server.Client/Components/IByondClient.cs +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -7,10 +7,17 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { /// - /// For managing the BYOND installation + /// For managing the installation /// - public interface IByondClient : IClient + public interface IByondClient : IRightsClient { + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); + /// /// Updates the installed BYOND /// @@ -19,13 +26,5 @@ namespace Tgstation.Server.Client.Components /// The for the operation /// A representing the running operation Task Update(Byond byond, Action progressCallback, CancellationToken cancellationToken); - - /// - /// Get the currently installed BYOND - /// - /// Read the staged instead if applicable - /// The for the operation - /// A resulting in the installed BYOND version or if none is installed - Task GetVersion(bool staged, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 5710aadda9..24338095c8 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -1,16 +1,30 @@ -using Tgstation.Server.Api.Models; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { /// - /// for server instances + /// Client for s /// - public interface IInstanceClient : IClient + public interface IInstanceClient : IRightsClient { + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); + /// /// Access the /// IByondClient Byond { get; } + + /// + /// Access the + /// + IRepositoryClient Repository { get; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs index f4197a8bfe..f4e747d4f5 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Client.Components /// /// Manager for s /// - public interface IInstanceManagerClient : IClient> + public interface IInstanceManagerClient : IRightsClient> { /// /// Create an diff --git a/src/Tgstation.Server.Client/Components/IRepositoryClient.cs b/src/Tgstation.Server.Client/Components/IRepositoryClient.cs new file mode 100644 index 0000000000..a3ee2188ad --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IRepositoryClient.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// For managing the + /// + public interface IRepositoryClient : IRightsClient + { + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); + + /// + /// Update the + /// + /// The to update + /// Optional action to take when progress is reported. Will either not be called for some operations, or be called with the numbers 1-100 + /// The for the operation + /// A representing the running operation + Task Update(Repository repository, Action progressCallback, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs index c93a853944..ff4c6654b9 100644 --- a/src/Tgstation.Server.Client/Components/ITokenClient.cs +++ b/src/Tgstation.Server.Client/Components/ITokenClient.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Client.Components /// /// for managing s /// - public interface ITokenClient: IClient + public interface ITokenClient: IRightsClient { /// /// Gets all active s for the diff --git a/src/Tgstation.Server.Client/IClient.cs b/src/Tgstation.Server.Client/IClient.cs deleted file mode 100644 index 8fd0598243..0000000000 --- a/src/Tgstation.Server.Client/IClient.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Tgstation.Server.Client -{ - /// - /// Basic client - /// - /// The for the - /// Which of the the represents - public interface IClient - { - /// - /// Get the for the - /// - /// The for the operation - /// A resulting in the for the - Task Rights(CancellationToken cancellationToken); - - /// - /// Read the of the - /// - /// The for the operation - /// The of the - Task Read(CancellationToken cancellationToken); - } -} diff --git a/src/Tgstation.Server.Client/IRightsClient.cs b/src/Tgstation.Server.Client/IRightsClient.cs new file mode 100644 index 0000000000..bc32b24a35 --- /dev/null +++ b/src/Tgstation.Server.Client/IRightsClient.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Client +{ + /// + /// client that has a bitset + /// + /// The for the + public interface IRightsClient + { + /// + /// Get the for the + /// + /// The for the operation + /// A resulting in the for the + Task Rights(CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index 406ae13b26..cd0b17e357 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -16,12 +16,17 @@ namespace Tgstation.Server.Client Version Version { get; } /// - /// The connection timeout in milliseconds + /// The connection timeout in milliseconds. Defaults to 10000 /// int Timeout { get; set; } /// - /// The requery rate for job updates in milliseconds + /// How long to return initially cached models for in seconds. Defaults to 60 + /// + int CacheExpiry { get; set; } + + /// + /// The requery rate for job updates in milliseconds. Defaults to 5000 /// int RequeryRate { get; set; } diff --git a/tgstation-server.sln b/tgstation-server.sln index c2d4b4770c..9c2f476263 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6 ProjectSection(SolutionItems) = preProject build\BuildDox.ps1 = build\BuildDox.ps1 build\Dockerfile = build\Dockerfile + build\Dockerfile1709 = build\Dockerfile1709 build\Doxyfile = build\Doxyfile build\tgs.ico = build\tgs.ico build\UploadCoverage.ps1 = build\UploadCoverage.ps1 From 22237bc3b572afd5110ee46940f14028101d877d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 2 Apr 2018 16:15:10 -0400 Subject: [PATCH 025/397] DreamDaemon model, unify tokens. Fix others --- .../Models/DreamDaemon.cs | 70 +++++++++++++++++++ .../Models/DreamDaemonSecurity.cs | 21 ++++++ .../Models/DreamDaemonStatus.cs | 23 ++++++ src/Tgstation.Server.Api/Models/Instance.cs | 2 +- src/Tgstation.Server.Api/Models/Repository.cs | 2 +- src/Tgstation.Server.Api/Models/Token.cs | 25 +++++-- src/Tgstation.Server.Api/Models/TokenInfo.cs | 25 ------- .../Rights/TokenRights.cs | 2 +- .../Components/IInstanceManagerClient.cs | 9 ++- .../Components/ITokenClient.cs | 23 +++--- 10 files changed, 161 insertions(+), 41 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/DreamDaemon.cs create mode 100644 src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs create mode 100644 src/Tgstation.Server.Api/Models/DreamDaemonStatus.cs delete mode 100644 src/Tgstation.Server.Api/Models/TokenInfo.cs diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs new file mode 100644 index 0000000000..4394923f37 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents an instance of BYOND's DreamDaemon game server + /// + public sealed class DreamDaemon + { + /// + /// The pull requests merged on the live game version. Not modifiable + /// + IReadOnlyDictionary PullRequests { get; set; } + + /// + /// The git sha the live game version was compiled at. Not modifiable + /// + string Sha { get; set; } + + /// + /// The git sha of the origin branch the live game version was compiled at. Not modifiable + /// + string OriginSha { get; set; } + + /// + /// If starts when it's starts + /// + bool? AutoStart { get; set; } + + /// + /// The current status of + /// + DreamDaemonStatus? Status { get; set; } + + /// + /// If the BYOND web client can be used to connect to the game server + /// + bool? AllowWebClient { get; set; } + + /// + /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields + /// + bool? SoftRestart { get; set; } + + /// + /// If the server is undergoing a soft shutdown + /// + bool? SoftShutdown { get; set; } + + /// + /// The level of + /// + DreamDaemonSecurity? SecurirtyLevel { get; set; } + + /// + /// The first port uses. This should be the publically advertised port + /// + ushort? PrimaryPort { get; set; } + + /// + /// The second port uses + /// + ushort? SecondaryPort { get; set; } + + /// + /// The port the running instance is set to. Not modifiable + /// + bool CurrentPort { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs new file mode 100644 index 0000000000..fef6a8f20c --- /dev/null +++ b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs @@ -0,0 +1,21 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// DreamDaemon's security level + /// + public enum DreamDaemonSecurity + { + /// + /// Server is unrestricted in terms of file access and shell commands + /// + Trusted = 0, + /// + /// Server will not be able to run shell commands or access files outside it's working directory + /// + Safe, + /// + /// Server will not be able to run shell commands or access anything but temporary files + /// + Ultrasafe + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/DreamDaemonStatus.cs b/src/Tgstation.Server.Api/Models/DreamDaemonStatus.cs new file mode 100644 index 0000000000..a09c75d399 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/DreamDaemonStatus.cs @@ -0,0 +1,23 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// The status of + /// +#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names + public enum DreamDaemonStatus +#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names + { + /// + /// Server is not running + /// + Offline, + /// + /// Server is being rebooted + /// + HardRebooting, + /// + /// Server is running + /// + Online, + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 42fbc3c710..33dab2786a 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -6,7 +6,7 @@ public sealed class Instance { /// - /// The id of the + /// The id of the . Not modifiable /// public long Id { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 17591cf066..8df5c02a11 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -33,7 +33,7 @@ namespace Tgstation.Server.Api.Models public string CommitterEmail { get; set; } /// - /// Associated list of tag name -> sha for repository compiles + /// Associated list of tag name -> sha for repository compiles. Not modifiable /// public IReadOnlyDictionary Backups { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index c8f34cc70b..21a5363cea 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,12 +1,29 @@ -namespace Tgstation.Server.Api.Models +using System.Net; + +namespace Tgstation.Server.Api.Models { /// - /// An access token + /// Represents an access token for the server /// - public sealed class Token : TokenInfo + public sealed class Token { /// - /// The token + /// The id of the . Not modifiable + /// + public long Id { get; } + + /// + /// The user agent that created the + /// + public string ClientUserAgent { get; set; } + + /// + /// The the was originally issued to + /// + public IPAddress IssuedTo { get; set; } + + /// + /// The token . Not modifiable, only appears once /// public string Value { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/TokenInfo.cs b/src/Tgstation.Server.Api/Models/TokenInfo.cs deleted file mode 100644 index e458065a21..0000000000 --- a/src/Tgstation.Server.Api/Models/TokenInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Net; - -namespace Tgstation.Server.Api.Models -{ - /// - /// Information about a - /// - public class TokenInfo - { - /// - /// The id of the - /// - public long Id { get; } - - /// - /// The user agent that created the - /// - public string ClientUserAgent { get; set; } - - /// - /// The the was originally issued to - /// - public IPAddress IssuedTo { get; set; } - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/TokenRights.cs b/src/Tgstation.Server.Api/Rights/TokenRights.cs index ba958fd380..98f5bf3f03 100644 --- a/src/Tgstation.Server.Api/Rights/TokenRights.cs +++ b/src/Tgstation.Server.Api/Rights/TokenRights.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Api.Rights /// Delete = 2, /// - /// User can list all their s + /// User can list all their s /// List = 4, /// diff --git a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs index f4e747d4f5..a5fbfc3584 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs @@ -9,8 +9,15 @@ namespace Tgstation.Server.Client.Components /// /// Manager for s /// - public interface IInstanceManagerClient : IRightsClient> + public interface IInstanceManagerClient : IRightsClient { + /// + /// Get all s for s the user can view + /// + /// The for the operation + /// A resulting in a of all s for s the user can view + Task> Read(CancellationToken cancellationToken); + /// /// Create an /// diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs index ff4c6654b9..a563456217 100644 --- a/src/Tgstation.Server.Client/Components/ITokenClient.cs +++ b/src/Tgstation.Server.Client/Components/ITokenClient.cs @@ -9,26 +9,33 @@ namespace Tgstation.Server.Client.Components /// /// for managing s /// - public interface ITokenClient: IRightsClient + public interface ITokenClient: IRightsClient { /// - /// Gets all active s for the + /// Generate a new for the connected user, expiring the current one if applicable /// /// The for the operation - /// A resulting a of active s for the - Task> GetClientInfos(CancellationToken cancellationToken); + /// A resulting in the new + Task Read(CancellationToken cancellationToken); /// - /// Gets all active s for all users + /// Gets all active s for the /// /// The for the operation - /// A resulting a of active s for the keyed by username - Task> GetAllInfos(CancellationToken cancellationToken); + /// A resulting a of active s for the + Task> GetClientInfos(CancellationToken cancellationToken); + + /// + /// Gets all active s for all users + /// + /// The for the operation + /// A resulting a of active s for the keyed by username + Task> GetAllInfos(CancellationToken cancellationToken); /// /// Invalidate the specified active designated by /// - /// The of the to invalidate + /// The of the to invalidate /// The for the operation /// A representing the running operation Task Invalidate(long tokenId, CancellationToken cancellationToken); From f3f77c7cf93daa7f33889893da147bc9355430f7 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 2 Apr 2018 17:09:54 -0400 Subject: [PATCH 026/397] Administration model --- .../Models/Administration.cs | 27 +++++++++ .../Models/DreamDaemon.cs | 26 ++++---- src/Tgstation.Server.Api/Models/User.cs | 38 ++++++++++++ .../Rights/AdministrationRights.cs | 28 +++++++++ .../Rights/DreamDaemonRights.cs | 60 +++++++++++++++++++ .../Components/IDreamDaemonClient.cs | 42 +++++++++++++ .../Components/IInstanceClient.cs | 2 +- .../Components/IInstanceManagerClient.cs | 4 +- 8 files changed, 211 insertions(+), 16 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Administration.cs create mode 100644 src/Tgstation.Server.Api/Models/User.cs create mode 100644 src/Tgstation.Server.Api/Rights/AdministrationRights.cs create mode 100644 src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs create mode 100644 src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs new file mode 100644 index 0000000000..668ba0dd0a --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Metadata about an installation + /// + public sealed class Administration + { + /// + /// Use the specified Windows/UNIX authentication to authorize users. Setting this to enables full administrative anonymous access + /// + public string SystemAuthenticationGroup { get; set; } + + /// + /// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information. + /// + public bool EnableTelemetry { get; set; } + + /// + /// Users in the . Not modifiable + /// + public IReadOnlyList Users { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 4394923f37..974bf1d83e 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -10,61 +10,61 @@ namespace Tgstation.Server.Api.Models /// /// The pull requests merged on the live game version. Not modifiable /// - IReadOnlyDictionary PullRequests { get; set; } + public IReadOnlyDictionary PullRequests { get; set; } /// /// The git sha the live game version was compiled at. Not modifiable /// - string Sha { get; set; } + public string Sha { get; set; } /// /// The git sha of the origin branch the live game version was compiled at. Not modifiable /// - string OriginSha { get; set; } - + public string OriginSha { get; set; } + /// /// If starts when it's starts /// - bool? AutoStart { get; set; } + public bool? AutoStart { get; set; } /// /// The current status of /// - DreamDaemonStatus? Status { get; set; } + public DreamDaemonStatus? Status { get; set; } /// /// If the BYOND web client can be used to connect to the game server /// - bool? AllowWebClient { get; set; } + public bool? AllowWebClient { get; set; } /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields /// - bool? SoftRestart { get; set; } + public bool? SoftRestart { get; set; } /// /// If the server is undergoing a soft shutdown /// - bool? SoftShutdown { get; set; } + public bool? SoftShutdown { get; set; } /// /// The level of /// - DreamDaemonSecurity? SecurirtyLevel { get; set; } + public DreamDaemonSecurity? SecurirtyLevel { get; set; } /// /// The first port uses. This should be the publically advertised port /// - ushort? PrimaryPort { get; set; } + public ushort? PrimaryPort { get; set; } /// /// The second port uses /// - ushort? SecondaryPort { get; set; } + public ushort? SecondaryPort { get; set; } /// /// The port the running instance is set to. Not modifiable /// - bool CurrentPort { get; set; } + public bool CurrentPort { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs new file mode 100644 index 0000000000..82699749a9 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a server + /// + public sealed class User + { + /// + /// The system identifier for the . On Windows, this is the user's SecurityIdentifier. On UNIX, this is their UID + /// + public string SystemIdentifier { get; set; } + + /// + /// The name of the + /// + public string Name { get; set; } + + /// + /// The for the + /// + public AdministrationRights AdministrationRights { get; set; } + + /// + /// The for the + /// + public InstanceManagerRights InstanceManagerRights { get; set; } + + /// + /// of s the can view + /// +#pragma warning disable CA2227 // Collection properties should be read only + public List ViewableInstanceIds { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs new file mode 100644 index 0000000000..da7cb3d559 --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -0,0 +1,28 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights for + /// + [Flags] + public enum AdministrationRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User can change + /// + ChangeAuthenticationGroup = 1, + /// + /// User can change + /// + ChangeTelemetry = 2, + /// + /// User can edit themself and other s + /// + EditUsers = 4, + } +} diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs new file mode 100644 index 0000000000..51591eb334 --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -0,0 +1,60 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights for + /// + [Flags] + public enum DreamDaemonRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User can read , , and + /// + ReadRevision = 1, + /// + /// User can change both primary and secondary ports + /// + SetPorts = 2, + /// + /// User can change + /// + SetAutoStart = 4, + /// + /// User set + /// + SetSecurity = 8, + /// + /// User can read all ports, , , , , and + /// + ReadMetadata = 16, + /// + /// User can change + /// + SetWebClient = 32, + /// + /// User can enable + /// + SoftRestart = 64, + /// + /// User can enable + /// + SoftShutdown = 128, + /// + /// User can immediately restart + /// + Restart = 256, + /// + /// User can immediately shutdown + /// + Shutdown = 512, + /// + /// User can start and disable and + /// + Start = 1024 + } +} diff --git a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs new file mode 100644 index 0000000000..fb4a0d38d3 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs @@ -0,0 +1,42 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// For managing + /// + interface IDreamDaemonClient: IRightsClient + { + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); + + /// + /// Start + /// + /// The for the operation + /// A representing the running operation + Task Start(CancellationToken cancellationToken); + + /// + /// Stop + /// + /// The for the operation + /// A representing the running operation + Task Stop(CancellationToken cancellationToken); + + /// + /// Update . This may trigger + /// + /// The to update + /// The for the operation + /// A representing the running operation + Task Update(DreamDaemon dreamDaemon, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 24338095c8..18d1252c65 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -6,7 +6,7 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { /// - /// Client for s + /// For managing a single /// public interface IInstanceClient : IRightsClient { diff --git a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs index a5fbfc3584..4e87ed6d46 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs @@ -7,9 +7,9 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { /// - /// Manager for s + /// For managing s /// - public interface IInstanceManagerClient : IRightsClient + public interface IInstanceManagerClient : IRightsClient { /// /// Get all s for s the user can view From a93643c1dcff2eef2187a2d6e1a7ac47b7472e2b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 3 Apr 2018 09:01:31 -0400 Subject: [PATCH 027/397] Minor user tweak --- src/Tgstation.Server.Api/Models/User.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index 82699749a9..6f96a1a5e6 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -29,10 +29,10 @@ namespace Tgstation.Server.Api.Models public InstanceManagerRights InstanceManagerRights { get; set; } /// - /// of s the can view + /// of s the can view mapped to a bool representing if they can set permissions for s within that /// #pragma warning disable CA2227 // Collection properties should be read only - public List ViewableInstanceIds { get; set; } + public Dictionary ViewableInstanceIds { get; set; } #pragma warning restore CA2227 // Collection properties should be read only } } \ No newline at end of file From 30c8e65c728143a7a25985b81840801a814fde29 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 3 Apr 2018 12:33:45 -0400 Subject: [PATCH 028/397] Make instance users --- .../Models/InstanceUser.cs | 27 +++++++++++++++++ src/Tgstation.Server.Api/Models/User.cs | 9 +----- .../Rights/InstanceRights.cs | 20 ------------- .../Rights/InstanceUserRights.cs | 24 +++++++++++++++ .../Components/IDreamDaemonClient.cs | 6 ++-- .../Components/IInstanceClient.cs | 29 ++++++++++++------- .../Components/IInstanceManagerClient.cs | 2 +- .../Components/IInstanceUserClient.cs | 29 +++++++++++++++++++ 8 files changed, 104 insertions(+), 42 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/InstanceUser.cs delete mode 100644 src/Tgstation.Server.Api/Rights/InstanceRights.cs create mode 100644 src/Tgstation.Server.Api/Rights/InstanceUserRights.cs create mode 100644 src/Tgstation.Server.Client/Components/IInstanceUserClient.cs diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs new file mode 100644 index 0000000000..9e3734db2f --- /dev/null +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -0,0 +1,27 @@ +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models +{ + public sealed class InstanceUser + { + /// + /// See definition in + /// + public string SystemIdentifier { get; set; } + + /// + /// The of the + /// + public ByondRights ByondRights { get; set; } + + /// + /// The of the + /// + DreamDaemonRights DreamDaemonRights { get; set; } + + /// + /// The of the + /// + RepositoryRights RepositoryRights { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index 6f96a1a5e6..47dc071dca 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Api.Models public sealed class User { /// - /// The system identifier for the . On Windows, this is the user's SecurityIdentifier. On UNIX, this is their UID + /// The system identifier for the . On Windows, this is the user's SecurityIdentifier. On UNIX, this is their UID. Not modifiable /// public string SystemIdentifier { get; set; } @@ -27,12 +27,5 @@ namespace Tgstation.Server.Api.Models /// The for the /// public InstanceManagerRights InstanceManagerRights { get; set; } - - /// - /// of s the can view mapped to a bool representing if they can set permissions for s within that - /// -#pragma warning disable CA2227 // Collection properties should be read only - public Dictionary ViewableInstanceIds { get; set; } -#pragma warning restore CA2227 // Collection properties should be read only } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/InstanceRights.cs b/src/Tgstation.Server.Api/Rights/InstanceRights.cs deleted file mode 100644 index 31dfa21825..0000000000 --- a/src/Tgstation.Server.Api/Rights/InstanceRights.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Tgstation.Server.Api.Rights -{ - /// - /// Rights for an - /// - [Flags] - public enum InstanceRights - { - /// - /// User has no rights - /// - None = 0, - /// - /// Allow access to - /// - Byond = 1, - } -} diff --git a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs new file mode 100644 index 0000000000..6bbef59b6c --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs @@ -0,0 +1,24 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights for an + /// + [Flags] + public enum InstanceUserRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// Allow read access to for the + /// + ReadUsers = 1, + /// + /// Allow write access to for the + /// + WriteUsers = 2, + } +} diff --git a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs index fb4a0d38d3..228055c9c2 100644 --- a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Client.Components /// /// For managing /// - interface IDreamDaemonClient: IRightsClient + public interface IDreamDaemonClient: IRightsClient { /// /// Get the represented by the @@ -25,11 +25,11 @@ namespace Tgstation.Server.Client.Components Task Start(CancellationToken cancellationToken); /// - /// Stop + /// Shutdown /// /// The for the operation /// A representing the running operation - Task Stop(CancellationToken cancellationToken); + Task Shutdown(CancellationToken cancellationToken); /// /// Update . This may trigger diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 18d1252c65..0e891f674f 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -1,22 +1,14 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { /// /// For managing a single /// - public interface IInstanceClient : IRightsClient + public interface IInstanceClient { - /// - /// Get the represented by the - /// - /// The for the operation - /// A resulting in the represented by the - Task Read(CancellationToken cancellationToken); - /// /// Access the /// @@ -26,5 +18,22 @@ namespace Tgstation.Server.Client.Components /// Access the /// IRepositoryClient Repository { get; } - } + + /// + /// Access the + /// + IDreamDaemonClient DreamDaemon { get; } + + /// + /// Access the + /// + IInstanceUserClient Users { get; } + + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); + } } \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs index 4e87ed6d46..edc8fb393a 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceManagerClient.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Client.Components /// /// For managing s /// - public interface IInstanceManagerClient : IRightsClient + public interface IInstanceManagerClient : IRightsClient { /// /// Get all s for s the user can view diff --git a/src/Tgstation.Server.Client/Components/IInstanceUserClient.cs b/src/Tgstation.Server.Client/Components/IInstanceUserClient.cs new file mode 100644 index 0000000000..68eae0275d --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IInstanceUserClient.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// For managing s + /// + public interface IInstanceUserClient : IRightsClient + { + /// + /// Get the s in the + /// + /// The for the operation + /// A resulting in a of s in the instance + Task> Read(CancellationToken cancellationToken); + + /// + /// Update a + /// + /// The to update + /// The for the operation + /// A representing the running operation + Task UpdateUser(InstanceUser instanceUser, CancellationToken cancellationToken); + } +} \ No newline at end of file From 0fcd5df18effb42fa821af8a4f6cdaf50cac0b5d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 3 Apr 2018 13:39:32 -0400 Subject: [PATCH 029/397] Compiler model --- src/Tgstation.Server.Api/Models/Compiler.cs | 45 +++++++++++++++++++ .../Models/CompilerStatus.cs | 27 +++++++++++ .../Models/DreamDaemon.cs | 8 +++- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Api/Models/Compiler.cs create mode 100644 src/Tgstation.Server.Api/Models/CompilerStatus.cs diff --git a/src/Tgstation.Server.Api/Models/Compiler.cs b/src/Tgstation.Server.Api/Models/Compiler.cs new file mode 100644 index 0000000000..39eaea40b9 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Compiler.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents the state of the DreamMaker compiler + /// + public sealed class Compiler + { + /// + /// When the compilation started + /// + public DateTimeOffset StartedAt { get; set; } + /// + /// When the compilation finished + /// + public DateTimeOffset FinishedAt { get; set; } + /// + /// The of the compiler + /// + public CompilerStatus Status { get; set; } + /// + /// If the compiler targeted the primary directory + /// + public bool? TargetedPrimaryDirectory { get; set; } + /// + /// Textual output of DM + /// + public string Output { get; set; } + /// + /// Exit code of DM + /// + public int? ExitCode { get; set; } + /// + /// Git revision the compiler ran on + /// + public string Revision { get; set; } + /// + /// Git revision of the origin branch the compiler ran on + /// + public string OriginRevision { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/CompilerStatus.cs b/src/Tgstation.Server.Api/Models/CompilerStatus.cs new file mode 100644 index 0000000000..b2c93b82f4 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/CompilerStatus.cs @@ -0,0 +1,27 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// Status of the for an + /// +#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names + public enum CompilerStatus +#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names + { + /// + /// The is idle + /// + Idle, + /// + /// The directiory is being targeted + /// + Targeting, + /// + /// The is being copied + /// + Copying, + /// + /// DreamMaker is running + /// + Compiling + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 974bf1d83e..564eac09b0 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Tgstation.Server.Api.Models { @@ -66,5 +67,10 @@ namespace Tgstation.Server.Api.Models /// The port the running instance is set to. Not modifiable /// public bool CurrentPort { get; set; } + + /// + /// When the live revision was compiled. Not modifiable + /// + public DateTimeOffset CompiledOn { get; set; } } } From c1d8f077885f7f3a6266a9ad593fc43956c34eeb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 09:52:05 -0400 Subject: [PATCH 030/397] Compiler rights + cleanup --- src/Tgstation.Server.Api/Models/Compiler.cs | 17 ++++++---- .../Models/DreamDaemon.cs | 2 +- src/Tgstation.Server.Api/Models/Repository.cs | 4 +-- .../Rights/CompilerRights.cs | 32 +++++++++++++++++++ .../Rights/RepositoryRights.cs | 6 +++- 5 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 src/Tgstation.Server.Api/Rights/CompilerRights.cs diff --git a/src/Tgstation.Server.Api/Models/Compiler.cs b/src/Tgstation.Server.Api/Models/Compiler.cs index 39eaea40b9..605ee593b7 100644 --- a/src/Tgstation.Server.Api/Models/Compiler.cs +++ b/src/Tgstation.Server.Api/Models/Compiler.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Tgstation.Server.Api.Models { @@ -33,13 +31,18 @@ namespace Tgstation.Server.Api.Models /// Exit code of DM /// public int? ExitCode { get; set; } + /// + /// Git revision the compiler ran on. Not modifiable + /// + public string Revision { get; set; } /// - /// Git revision the compiler ran on - /// - public string Revision { get; set; } - /// - /// Git revision of the origin branch the compiler ran on + /// Git revision of the origin branch the compiler ran on. Not modifiable /// public string OriginRevision { get; set; } + + /// + /// How often the automatically compiles in minutes + /// + public int? AutoCompileInterval { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 564eac09b0..cb020c759a 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -71,6 +71,6 @@ namespace Tgstation.Server.Api.Models /// /// When the live revision was compiled. Not modifiable /// - public DateTimeOffset CompiledOn { get; set; } + public DateTimeOffset CompiledAt { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 8df5c02a11..87aafe3af9 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -18,9 +18,9 @@ namespace Tgstation.Server.Api.Models public string Sha { get; set; } /// - /// The branch HEAD points to + /// The branch or tag HEAD points to /// - public string Branch { get; set; } + public string Reference { get; set; } /// /// The name of the committer diff --git a/src/Tgstation.Server.Api/Rights/CompilerRights.cs b/src/Tgstation.Server.Api/Rights/CompilerRights.cs new file mode 100644 index 0000000000..494df4080c --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/CompilerRights.cs @@ -0,0 +1,32 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights for the + /// + [Flags] + public enum CompilerRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User may read status + /// + Read = 1, + /// + /// User may trigger compiles + /// + Compile = 2, + /// + /// User may cancel compiles + /// + CancelCompile = 4, + /// + /// User may modify + /// + SetAutoCompile = 8, + } +} diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index 4574b2ae6b..2dae519ade 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -55,6 +55,10 @@ namespace Tgstation.Server.Api.Rights /// /// User may read all fields in the with the exception of /// - Read = 2048 + Read = 2048, + /// + /// User may set to a backup tag + /// + CheckoutBackup, } } From d20e6d663ae9179291b3f5136a4c0966e3407c1d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 11:21:21 -0400 Subject: [PATCH 031/397] Change to attribute permissions --- src/Tgstation.Server.Api/ModelAttribute.cs | 73 ++++++++++++++++++ .../Models/Administration.cs | 11 ++- src/Tgstation.Server.Api/Models/Byond.cs | 5 ++ src/Tgstation.Server.Api/Models/Compiler.cs | 48 ------------ .../Models/CompilerStatus.cs | 4 +- .../Models/DreamDaemon.cs | 33 +++++--- src/Tgstation.Server.Api/Models/DreamMaker.cs | 59 ++++++++++++++ src/Tgstation.Server.Api/Models/Instance.cs | 9 ++- .../Models/InstanceUser.cs | 5 ++ src/Tgstation.Server.Api/Models/Repository.cs | 13 ++++ src/Tgstation.Server.Api/Models/Token.cs | 2 + src/Tgstation.Server.Api/Models/User.cs | 8 +- .../PermissionsAttribute.cs | 77 +++++++++++++++++++ ...{CompilerRights.cs => DreamMakerRights.cs} | 8 +- .../Rights/InstanceUserRights.cs | 4 +- 15 files changed, 286 insertions(+), 73 deletions(-) create mode 100644 src/Tgstation.Server.Api/ModelAttribute.cs delete mode 100644 src/Tgstation.Server.Api/Models/Compiler.cs create mode 100644 src/Tgstation.Server.Api/Models/DreamMaker.cs create mode 100644 src/Tgstation.Server.Api/PermissionsAttribute.cs rename src/Tgstation.Server.Api/Rights/{CompilerRights.cs => DreamMakerRights.cs} (67%) diff --git a/src/Tgstation.Server.Api/ModelAttribute.cs b/src/Tgstation.Server.Api/ModelAttribute.cs new file mode 100644 index 0000000000..face9e2617 --- /dev/null +++ b/src/Tgstation.Server.Api/ModelAttribute.cs @@ -0,0 +1,73 @@ +using System; + +namespace Tgstation.Server.Api +{ + /// + /// Indicates the rights for a model + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class ModelAttribute : Attribute + { + /// + /// Right required to read the field + /// + public object ReadRight { get; set; } + + /// + /// Right required to write the field with an Update call + /// + public object WriteRight + { + get => writeRight; + set + { + if (DenyWrite) + throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with DenyRight set"); + writeRight = value; + } + } + + /// + /// If the field cannot be written to + /// + public bool DenyWrite + { + get => denyWrite; + set + { + if (WriteRight != null) + throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with WriteRight set"); + denyWrite = value; + } + } + + /// + /// The enum type that designates access to the model. Must be an with the + /// + public Type RightsEnum { get; } + + /// + /// Construct a + /// + /// The value of + public ModelAttribute(Type rightsEnum) + { + if (!typeof(Enum).IsAssignableFrom(rightsEnum)) + throw new ArgumentException("rightsEnum must be an enum type!", nameof(rightsEnum)); + if (rightsEnum.GetCustomAttributes(typeof(FlagsAttribute), false).Length == 0) + throw new ArgumentException("rightsEnum must have the FlagsAttribute!", nameof(rightsEnum)); + if(Enum.GetUnderlyingType(rightsEnum) != typeof(int)) + throw new ArgumentException("rightsEnum must be an integer type!", nameof(rightsEnum)); + RightsEnum = rightsEnum; + } + + /// + /// Backing field for + /// + object writeRight; + /// + /// Backing field for + /// + bool denyWrite; + } +} diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 668ba0dd0a..d65202d3c3 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -1,27 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// /// Metadata about an installation /// + [Model(typeof(AdministrationRights))] public sealed class Administration { /// /// Use the specified Windows/UNIX authentication to authorize users. Setting this to enables full administrative anonymous access /// + [Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)] public string SystemAuthenticationGroup { get; set; } /// /// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information. /// + [Permissions(ReadRight = AdministrationRights.ChangeTelemetry, WriteRight = AdministrationRights.ChangeTelemetry)] public bool EnableTelemetry { get; set; } /// - /// Users in the . Not modifiable + /// Users in the /// + [Permissions(DenyWrite = true)] public IReadOnlyList Users { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index be91273f66..0600435cce 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -1,25 +1,30 @@ using System; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// /// Represents a BYOND installation /// + [Model(typeof(ByondRights))] public sealed class Byond { /// /// The for the installation /// + [Permissions(DenyWrite = true, ReadRight = ByondRights.ReadStatus)] public ByondStatus ByondStatus { get; set; } /// /// The of the installation. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers /// + [Permissions(ComplexWrite = true, ReadRight = ByondRights.ReadInstalled)] public Version Version { get; set; } /// /// The of the that's staged for installation /// + [Permissions(DenyWrite = true, ReadRight = ByondRights.ReadStaged)] public Version StagedVersion { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Compiler.cs b/src/Tgstation.Server.Api/Models/Compiler.cs deleted file mode 100644 index 605ee593b7..0000000000 --- a/src/Tgstation.Server.Api/Models/Compiler.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace Tgstation.Server.Api.Models -{ - /// - /// Represents the state of the DreamMaker compiler - /// - public sealed class Compiler - { - /// - /// When the compilation started - /// - public DateTimeOffset StartedAt { get; set; } - /// - /// When the compilation finished - /// - public DateTimeOffset FinishedAt { get; set; } - /// - /// The of the compiler - /// - public CompilerStatus Status { get; set; } - /// - /// If the compiler targeted the primary directory - /// - public bool? TargetedPrimaryDirectory { get; set; } - /// - /// Textual output of DM - /// - public string Output { get; set; } - /// - /// Exit code of DM - /// - public int? ExitCode { get; set; } - /// - /// Git revision the compiler ran on. Not modifiable - /// - public string Revision { get; set; } - /// - /// Git revision of the origin branch the compiler ran on. Not modifiable - /// - public string OriginRevision { get; set; } - - /// - /// How often the automatically compiles in minutes - /// - public int? AutoCompileInterval { get; set; } - } -} diff --git a/src/Tgstation.Server.Api/Models/CompilerStatus.cs b/src/Tgstation.Server.Api/Models/CompilerStatus.cs index b2c93b82f4..8ca0b48143 100644 --- a/src/Tgstation.Server.Api/Models/CompilerStatus.cs +++ b/src/Tgstation.Server.Api/Models/CompilerStatus.cs @@ -1,14 +1,14 @@ namespace Tgstation.Server.Api.Models { /// - /// Status of the for an + /// Status of the for an /// #pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names public enum CompilerStatus #pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names { /// - /// The is idle + /// The is idle /// Idle, /// diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index cb020c759a..e70e01e48c 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -1,76 +1,91 @@ using System; using System.Collections.Generic; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// /// Represents an instance of BYOND's DreamDaemon game server /// + [Model(typeof(DreamDaemonRights))] public sealed class DreamDaemon { /// - /// The pull requests merged on the live game version. Not modifiable + /// The pull requests merged on the live game version /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] public IReadOnlyDictionary PullRequests { get; set; } /// - /// The git sha the live game version was compiled at. Not modifiable + /// The git sha the live game version was compiled at /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] public string Sha { get; set; } /// - /// The git sha of the origin branch the live game version was compiled at. Not modifiable + /// The git sha of the origin branch the live game version was compiled at /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] public string OriginSha { get; set; } + /// + /// When the live revision was compiled + /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] + public DateTimeOffset CompiledAt { get; set; } + /// /// If starts when it's starts /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] public bool? AutoStart { get; set; } /// /// The current status of /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] public DreamDaemonStatus? Status { get; set; } /// /// If the BYOND web client can be used to connect to the game server /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] public bool? AllowWebClient { get; set; } /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)] public bool? SoftRestart { get; set; } /// /// If the server is undergoing a soft shutdown /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] public bool? SoftShutdown { get; set; } /// /// The level of /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] public DreamDaemonSecurity? SecurirtyLevel { get; set; } /// /// The first port uses. This should be the publically advertised port /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] public ushort? PrimaryPort { get; set; } /// /// The second port uses /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] public ushort? SecondaryPort { get; set; } /// - /// The port the running instance is set to. Not modifiable + /// The port the running instance is set to /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] public bool CurrentPort { get; set; } - - /// - /// When the live revision was compiled. Not modifiable - /// - public DateTimeOffset CompiledAt { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs new file mode 100644 index 0000000000..ccca180148 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -0,0 +1,59 @@ +using System; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents the state of the DreamMaker compiler + /// + [Model(typeof(DreamMakerRights), ReadRight = DreamMakerRights.Read)] + public sealed class DreamMaker + { + /// + /// When the compilation started + /// + [Permissions(DenyWrite = true)] + public DateTimeOffset StartedAt { get; set; } + /// + /// When the compilation finished + /// + [Permissions(DenyWrite = true)] + public DateTimeOffset FinishedAt { get; set; } + /// + /// The of the compiler + /// + [Permissions(DenyWrite = true)] + public CompilerStatus Status { get; set; } + /// + /// If the compiler targeted the primary directory + /// + [Permissions(DenyWrite = true)] + public bool? TargetedPrimaryDirectory { get; set; } + /// + /// Textual output of DM + /// + [Permissions(DenyWrite = true)] + public string Output { get; set; } + /// + /// Exit code of DM + /// + [Permissions(DenyWrite = true)] + public int? ExitCode { get; set; } + /// + /// Git revision the compiler ran on. Not modifiable + /// + [Permissions(DenyWrite = true)] + public string Revision { get; set; } + /// + /// Git revision of the origin branch the compiler ran on. Not modifiable + /// + [Permissions(DenyWrite = true)] + public string OriginRevision { get; set; } + + /// + /// How often the automatically compiles in minutes + /// + [Permissions(WriteRight = DreamMakerRights.SetAutoCompile)] + public int? AutoCompileInterval { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 33dab2786a..ca0e060ee4 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -1,28 +1,35 @@ -namespace Tgstation.Server.Api.Models +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models { /// /// Metadata about a server instance /// + [Model(typeof(InstanceManagerRights))] public sealed class Instance { /// /// The id of the . Not modifiable /// + [Permissions(DenyWrite = true)] public long Id { get; set; } /// /// The name of the /// + [Permissions(WriteRight = InstanceManagerRights.Rename)] public string Name { get; set; } /// /// The path to where the is located /// + [Permissions(WriteRight = InstanceManagerRights.Relocate)] public string Path { get; set; } /// /// If the is online /// + [Permissions(ComplexWrite = true)] public bool Online { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 9e3734db2f..3a79a434b0 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -2,11 +2,16 @@ namespace Tgstation.Server.Api.Models { + /// + /// Represents a s permissions in an + /// + [Model(typeof(InstanceUserRights), WriteRight = InstanceUserRights.WriteUsers)] public sealed class InstanceUser { /// /// See definition in /// + [Permissions(DenyWrite = true)] public string SystemIdentifier { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 87aafe3af9..4a36fab2e0 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -1,67 +1,80 @@ using System.Collections.Generic; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// /// Represents a git repository /// + [Model(typeof(RepositoryRights), ReadRight = RepositoryRights.Read)] public sealed class Repository { /// /// The origin URL. If , the does not exist /// + [Permissions(ComplexWrite = true)] public string Origin { get; set; } /// /// The commit HEAD points to /// + [Permissions(WriteRight = RepositoryRights.SetSha)] public string Sha { get; set; } /// /// The branch or tag HEAD points to /// + [Permissions(ComplexWrite = true)] public string Reference { get; set; } /// /// The name of the committer /// + [Permissions(WriteRight = RepositoryRights.ChangeCommitter)] public string CommitterName { get; set; } /// /// The e-mail of the committer /// + [Permissions(WriteRight = RepositoryRights.ChangeCommitter)] public string CommitterEmail { get; set; } /// /// Associated list of tag name -> sha for repository compiles. Not modifiable /// + [Permissions(DenyWrite = true)] public IReadOnlyDictionary Backups { get; set; } /// /// Associated list of GitHub pull request number -> sha for merged pull requests. Adding a value to this list will merge the latest commit of the pull request numbered by the key /// #pragma warning disable CA2227 // Collection properties should be read only + [Permissions(ComplexWrite = true)] public Dictionary PullRequests { get; set; } #pragma warning restore CA2227 // Collection properties should be read only /// /// The username to access the git repository with /// + [Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)] public string AccessUser { get; set; } /// /// The token/password to access the git repository with /// + [Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)] public string AccessToken { get; set; } /// /// If commits created from testmerges are pushed to the remote /// + [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] public bool PushTestMergeCommits { get; set; } /// /// How often the automatically updates in minutes /// + [Permissions(WriteRight = RepositoryRights.ChangeAutoUpdate)] public int? AutoUpdateInterval { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index 21a5363cea..0a23af5734 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,10 +1,12 @@ using System.Net; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// /// Represents an access token for the server /// + [Model(typeof(TokenRights), DenyWrite = true)] public sealed class Token { /// diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index 47dc071dca..6f8ac34e26 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -1,21 +1,23 @@ -using System.Collections.Generic; -using Tgstation.Server.Api.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// /// Represents a server /// + [Model(typeof(AdministrationRights), WriteRight = AdministrationRights.EditUsers)] public sealed class User { /// - /// The system identifier for the . On Windows, this is the user's SecurityIdentifier. On UNIX, this is their UID. Not modifiable + /// The system identifier for the . On Windows, this is the user's SecurityIdentifier. On UNIX, this is their UID /// + [Permissions(DenyWrite = true)] public string SystemIdentifier { get; set; } /// /// The name of the /// + [Permissions(DenyWrite = true)] public string Name { get; set; } /// diff --git a/src/Tgstation.Server.Api/PermissionsAttribute.cs b/src/Tgstation.Server.Api/PermissionsAttribute.cs new file mode 100644 index 0000000000..ee100a34e5 --- /dev/null +++ b/src/Tgstation.Server.Api/PermissionsAttribute.cs @@ -0,0 +1,77 @@ +using System; + +namespace Tgstation.Server.Api +{ + /// + /// Indicates permissions for model fields + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class PermissionsAttribute : Attribute + { + /// + /// Right required to read the field + /// + public object ReadRight { get; set; } + + /// + /// Right required to write the field with an Update call + /// + public object WriteRight + { + get => writeRight; + set + { + if (DenyWrite) + throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with DenyRight set"); + if (ComplexWrite) + throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with ComplexWrite set"); + writeRight = value; + } + } + + /// + /// If the field cannot be written to + /// + public bool DenyWrite + { + get => denyWrite; + set + { + if (WriteRight != null) + throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with WriteRight set"); + if (ComplexWrite) + throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with ComplexWrite set"); + denyWrite = value; + } + } + + /// + /// If the field has multiple write permissions + /// + public bool ComplexWrite + { + get => complexWrite; + set + { + if (WriteRight != null) + throw new InvalidOperationException("Cannot set ComplexWrite on a PermissionsAttribute with WriteRight set"); + if (ComplexWrite) + throw new InvalidOperationException("Cannot set ComplexWrite on a PermissionsAttribute with DenyWrite set"); + complexWrite = value; + } + } + + /// + /// Backing field for + /// + object writeRight; + /// + /// Backing field for + /// + bool denyWrite; + /// + /// Backing field for + /// + bool complexWrite; + } +} diff --git a/src/Tgstation.Server.Api/Rights/CompilerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs similarity index 67% rename from src/Tgstation.Server.Api/Rights/CompilerRights.cs rename to src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index 494df4080c..77dd9b3bae 100644 --- a/src/Tgstation.Server.Api/Rights/CompilerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -3,17 +3,17 @@ namespace Tgstation.Server.Api.Rights { /// - /// Rights for the + /// Rights for /// [Flags] - public enum CompilerRights + public enum DreamMakerRights { /// /// User has no rights /// None = 0, /// - /// User may read status + /// User may read status /// Read = 1, /// @@ -25,7 +25,7 @@ namespace Tgstation.Server.Api.Rights /// CancelCompile = 4, /// - /// User may modify + /// User may modify /// SetAutoCompile = 8, } diff --git a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs index 6bbef59b6c..26ac4d26ab 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs @@ -13,11 +13,11 @@ namespace Tgstation.Server.Api.Rights /// None = 0, /// - /// Allow read access to for the + /// Allow read access to for the /// ReadUsers = 1, /// - /// Allow write access to for the + /// Allow write access to for the /// WriteUsers = 2, } From 4e3cb4b186a5b789957d7f293eaae6dc0d248adf Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 14:16:56 -0400 Subject: [PATCH 032/397] More attribute stuff --- src/Tgstation.Server.Api/ModelAttribute.cs | 52 ++++------ src/Tgstation.Server.Api/Models/Byond.cs | 2 +- .../Models/DreamDaemon.cs | 4 +- src/Tgstation.Server.Api/Models/DreamMaker.cs | 12 +-- src/Tgstation.Server.Api/Models/Instance.cs | 2 +- .../Models/InstanceUser.cs | 16 +-- src/Tgstation.Server.Api/Models/Job.cs | 41 ++++++++ src/Tgstation.Server.Api/Models/Token.cs | 13 ++- .../PermissionsAttribute.cs | 54 +--------- .../Rights/ByondRights.cs | 16 +-- .../Rights/RepositoryRights.cs | 26 +++-- src/Tgstation.Server.Api/Route.cs | 20 ++++ src/Tgstation.Server.Api/RouteHelper.cs | 98 +++++++++++++++++++ 13 files changed, 225 insertions(+), 131 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Job.cs create mode 100644 src/Tgstation.Server.Api/Route.cs create mode 100644 src/Tgstation.Server.Api/RouteHelper.cs diff --git a/src/Tgstation.Server.Api/ModelAttribute.cs b/src/Tgstation.Server.Api/ModelAttribute.cs index face9e2617..74f9f2f161 100644 --- a/src/Tgstation.Server.Api/ModelAttribute.cs +++ b/src/Tgstation.Server.Api/ModelAttribute.cs @@ -9,43 +9,40 @@ namespace Tgstation.Server.Api public sealed class ModelAttribute : Attribute { /// - /// Right required to read the field + /// Right required to read the model /// public object ReadRight { get; set; } /// - /// Right required to write the field with an Update call + /// Right required to update the model /// - public object WriteRight - { - get => writeRight; - set - { - if (DenyWrite) - throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with DenyRight set"); - writeRight = value; - } - } + public object WriteRight { get; set; } /// - /// If the field cannot be written to + /// If the Create and Delete actions are available for this model /// - public bool DenyWrite - { - get => denyWrite; - set - { - if (WriteRight != null) - throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with WriteRight set"); - denyWrite = value; - } - } + public bool CanCrud { get; set; } + + /// + /// If the List action is available for this model + /// + public bool CanList { get; set; } + + /// + /// If the actions require an + /// + public bool RequiresInstance { get; set; } /// /// The enum type that designates access to the model. Must be an with the /// public Type RightsEnum { get; } + /// + /// Construct a + /// + public ModelAttribute() => CanList = true; + /// /// Construct a /// @@ -60,14 +57,5 @@ namespace Tgstation.Server.Api throw new ArgumentException("rightsEnum must be an integer type!", nameof(rightsEnum)); RightsEnum = rightsEnum; } - - /// - /// Backing field for - /// - object writeRight; - /// - /// Backing field for - /// - bool denyWrite; } } diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index 0600435cce..8ce05a3491 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Api.Models /// /// The of the installation. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers /// - [Permissions(ComplexWrite = true, ReadRight = ByondRights.ReadInstalled)] + [Permissions(ReadRight = ByondRights.ReadInstalled, WriteRight = ByondRights.ChangeVersion)] public Version Version { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index e70e01e48c..55056d5c58 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -5,9 +5,9 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// - /// Represents an instance of BYOND's DreamDaemon game server + /// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server /// - [Model(typeof(DreamDaemonRights))] + [Model(typeof(DreamDaemonRights), CanCrud = true)] public sealed class DreamDaemon { /// diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index ccca180148..5fcdb8fb51 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -4,14 +4,14 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// - /// Represents the state of the DreamMaker compiler + /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile /// - [Model(typeof(DreamMakerRights), ReadRight = DreamMakerRights.Read)] + [Model(typeof(DreamMakerRights), ReadRight = DreamMakerRights.Read, CanCrud = true)] public sealed class DreamMaker - { - /// - /// When the compilation started - /// + { + /// + /// When the compilation started + /// [Permissions(DenyWrite = true)] public DateTimeOffset StartedAt { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index ca0e060ee4..e9ca8c42b7 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Api.Models /// /// Metadata about a server instance /// - [Model(typeof(InstanceManagerRights))] + [Model(typeof(InstanceManagerRights), CanCrud = true, CanList = true)] public sealed class Instance { /// diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 3a79a434b0..9ef0173331 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -19,14 +19,14 @@ namespace Tgstation.Server.Api.Models /// public ByondRights ByondRights { get; set; } - /// - /// The of the - /// - DreamDaemonRights DreamDaemonRights { get; set; } + /// + /// The of the + /// + public DreamDaemonRights DreamDaemonRights { get; set; } - /// - /// The of the - /// - RepositoryRights RepositoryRights { get; set; } + /// + /// The of the + /// + public RepositoryRights RepositoryRights { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs new file mode 100644 index 0000000000..2aa7ac6c55 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -0,0 +1,41 @@ +using System; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a long running job on the server + /// + [Model] + public sealed class Job + { + /// + /// The ID + /// + public long Id { get; set; } + + /// + /// Human readable description of the + /// + public string Description { get; set; } + + /// + /// When the was started + /// + public DateTimeOffset StartedAt { get; set; } + + /// + /// When the stopped + /// + public DateTimeOffset StoppedAt { get; set; } + + /// + /// If the was cancelled + /// + public bool Cancelled { get; set; } + + /// + /// If the has incremental progress, this will range from 1 - 100. 0 otherwise + /// + public int Progress { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index 0a23af5734..7a8e573efa 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,12 +1,12 @@ -using System.Net; -using Tgstation.Server.Api.Rights; +using System; +using System.Net; namespace Tgstation.Server.Api.Models { /// - /// Represents an access token for the server + /// Represents an access token for the server. Read action generates a new one. Update action expires all for user /// - [Model(typeof(TokenRights), DenyWrite = true)] + [Model] public sealed class Token { /// @@ -24,6 +24,11 @@ namespace Tgstation.Server.Api.Models /// public IPAddress IssuedTo { get; set; } + /// + /// When the was originally issued + /// + public DateTimeOffset IssuedAt { get; set; } + /// /// The token . Not modifiable, only appears once /// diff --git a/src/Tgstation.Server.Api/PermissionsAttribute.cs b/src/Tgstation.Server.Api/PermissionsAttribute.cs index ee100a34e5..fd35005dcb 100644 --- a/src/Tgstation.Server.Api/PermissionsAttribute.cs +++ b/src/Tgstation.Server.Api/PermissionsAttribute.cs @@ -14,64 +14,18 @@ namespace Tgstation.Server.Api public object ReadRight { get; set; } /// - /// Right required to write the field with an Update call + /// Right required to update the field /// - public object WriteRight - { - get => writeRight; - set - { - if (DenyWrite) - throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with DenyRight set"); - if (ComplexWrite) - throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with ComplexWrite set"); - writeRight = value; - } - } + public object WriteRight { get; set; } /// /// If the field cannot be written to /// - public bool DenyWrite - { - get => denyWrite; - set - { - if (WriteRight != null) - throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with WriteRight set"); - if (ComplexWrite) - throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with ComplexWrite set"); - denyWrite = value; - } - } + public bool DenyWrite { get; set; } /// /// If the field has multiple write permissions /// - public bool ComplexWrite - { - get => complexWrite; - set - { - if (WriteRight != null) - throw new InvalidOperationException("Cannot set ComplexWrite on a PermissionsAttribute with WriteRight set"); - if (ComplexWrite) - throw new InvalidOperationException("Cannot set ComplexWrite on a PermissionsAttribute with DenyWrite set"); - complexWrite = value; - } - } - - /// - /// Backing field for - /// - object writeRight; - /// - /// Backing field for - /// - bool denyWrite; - /// - /// Backing field for - /// - bool complexWrite; + public bool ComplexWrite { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs index 0b96b9783a..4919ef2800 100644 --- a/src/Tgstation.Server.Api/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -21,24 +21,16 @@ namespace Tgstation.Server.Api.Rights /// ReadStaged = 2, /// - /// User may install any BYOND version if none is installed + /// User may change to any BYOND version /// - Install = 4, - /// - /// User may upgrade the installed BYOND version - /// - Upgrade = 8, - /// - /// User may downgrade the installed BYOND version - /// - Downgrade = 16, + ChangeVersion = 4, /// /// User may cancel a pending installation job /// - Cancel = 32, + Cancel = 8, /// /// User may read the of the installation job /// - ReadStatus = 64, + ReadStatus = 16, } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index 2dae519ade..a17ab1c6f8 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -15,50 +15,46 @@ namespace Tgstation.Server.Api.Rights /// /// User may create the if it does not exist /// - Create = 1, - /// - /// User may delete the - /// - Delete = 2, + SetOrigin = 2, /// /// User may directly set the sha the 's HEAD points to /// - SetSha = 8, + SetSha = 4, /// /// User may fetch and merge GitHub pull requests /// - MergePullRequest = 16, + MergePullRequest = 8, /// /// User may use the update feature /// - Update = 32, + UpdateBranch = 16, /// /// User may change and /// - ChangeCommitter = 64, + ChangeCommitter = 32, /// /// User may change /// - ChangeTestMergeCommits = 128, + ChangeTestMergeCommits = 64, /// /// User may change /// - ChangeAutoUpdate = 256, + ChangeAutoUpdate = 128, /// /// User may read and change and /// - ChangeCredentials = 512, + ChangeCredentials = 256, /// /// User may reset the 's HEAD to the origin reference of whatever branch it is tracking /// - Reset = 1024, + Reset = 512, /// /// User may read all fields in the with the exception of /// - Read = 2048, + Read = 1024, /// /// User may set to a backup tag /// - CheckoutBackup, + CheckoutBackup = 2048, } } diff --git a/src/Tgstation.Server.Api/Route.cs b/src/Tgstation.Server.Api/Route.cs new file mode 100644 index 0000000000..9a29119de5 --- /dev/null +++ b/src/Tgstation.Server.Api/Route.cs @@ -0,0 +1,20 @@ +using System.Net.Http; + +namespace Tgstation.Server.Api +{ + /// + /// Represents a route to a server action + /// + public sealed class Route + { + /// + /// The path to the action + /// + public string Path { get; set; } + + /// + /// The method of the action + /// + public HttpMethod Method { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/RouteHelper.cs b/src/Tgstation.Server.Api/RouteHelper.cs new file mode 100644 index 0000000000..de9c1f5cf1 --- /dev/null +++ b/src/Tgstation.Server.Api/RouteHelper.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using System.Net.Http; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Api +{ + /// + /// Gets routes for a given model + /// + public static class RouteHelper + { + /// + /// Read a + /// + /// The model type to read + /// The optional ID to pass in + /// A route to the read action + static Route Read(long? objectId) where TModel : class + { + var result = new Route { Path = String.Concat('/', typeof(TModel).Name), Method = HttpMethod.Get }; + if (objectId.HasValue) + result.Path = String.Concat(result.Path, '/', objectId); + return result; + } + + /// + /// Get the to the read action for a given + /// + /// The model to read + /// to read from if required + /// A to the read action + public static Route Read(Instance instance) where TModel : class + { + var model = (ModelAttribute)typeof(TModel).GetCustomAttributes(typeof(ModelAttribute), false).FirstOrDefault(); + if (model == default(ModelAttribute)) + throw new InvalidOperationException("TModel must have the ModelAttribute"); + if (model.RequiresInstance ^ instance != null) + throw (model.RequiresInstance ? new ArgumentNullException(nameof(instance)) : new ArgumentException("Instance is not used for this route!", nameof(instance))); + return Read(instance?.Id); + } + + /// + /// Get the to the update action for a given + /// + /// The model to update + /// A to the update action + public static Route Update(Instance instance) where TModel : class + { + var result = Read(instance); + result.Method = HttpMethod.Post; + return result; + } + + /// + /// Get the to the list action for a given + /// + /// The model to list + /// A to the list action + public static Route List(Instance instance) where TModel : class + { + var result = Read(instance); + result.Path = String.Concat(result.Path, '/', nameof(List)); + return result; + } + + /// + /// Get the to the create action for a given + /// + /// The model to create + /// A to the create action + public static Route Create(Instance instance) where TModel : class + { + var result = Read(instance); + result.Method = HttpMethod.Put; + return result; + } + + /// + /// Get the to the delete action for a given + /// + /// The model to delete + /// A to the delete action + public static Route Delete(Instance instance) where TModel : class + { + var result = Read(instance); + result.Method = HttpMethod.Delete; + return result; + } + + /// + /// Get the to a given + /// + /// The to get + /// A to the get action + public static Route GetJob(Job job) => Read(job.Id); + } +} From c25d2519235425e426272cfb110351b13bce7517 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 14:48:11 -0400 Subject: [PATCH 033/397] More api trimming --- src/Tgstation.Server.Api/ModelAttribute.cs | 20 +++----- .../Models/Administration.cs | 2 +- src/Tgstation.Server.Api/Models/Byond.cs | 2 +- .../Models/DreamDaemon.cs | 2 +- src/Tgstation.Server.Api/Models/DreamMaker.cs | 2 +- src/Tgstation.Server.Api/Models/Instance.cs | 2 +- .../Models/InstanceUser.cs | 6 +-- src/Tgstation.Server.Api/Models/Repository.cs | 2 +- src/Tgstation.Server.Api/Models/Token.cs | 3 +- src/Tgstation.Server.Api/Models/User.cs | 6 +-- .../Rights/RightsHelper.cs | 36 +++++++++++++ src/Tgstation.Server.Api/Rights/RightsType.cs | 50 +++++++++++++++++++ src/Tgstation.Server.Api/RouteHelper.cs | 18 +++++++ .../Tgstation.Server.Api.Tests/TestModels.cs | 37 ++++++++++++++ 14 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 src/Tgstation.Server.Api/Rights/RightsHelper.cs create mode 100644 src/Tgstation.Server.Api/Rights/RightsType.cs create mode 100644 tests/Tgstation.Server.Api.Tests/TestModels.cs diff --git a/src/Tgstation.Server.Api/ModelAttribute.cs b/src/Tgstation.Server.Api/ModelAttribute.cs index 74f9f2f161..6145721634 100644 --- a/src/Tgstation.Server.Api/ModelAttribute.cs +++ b/src/Tgstation.Server.Api/ModelAttribute.cs @@ -1,4 +1,5 @@ using System; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api { @@ -34,9 +35,9 @@ namespace Tgstation.Server.Api public bool RequiresInstance { get; set; } /// - /// The enum type that designates access to the model. Must be an with the + /// The the model uses /// - public Type RightsEnum { get; } + public RightsType RightsType { get; } /// /// Construct a @@ -44,18 +45,9 @@ namespace Tgstation.Server.Api public ModelAttribute() => CanList = true; /// - /// Construct a + /// Construct a with a given /// - /// The value of - public ModelAttribute(Type rightsEnum) - { - if (!typeof(Enum).IsAssignableFrom(rightsEnum)) - throw new ArgumentException("rightsEnum must be an enum type!", nameof(rightsEnum)); - if (rightsEnum.GetCustomAttributes(typeof(FlagsAttribute), false).Length == 0) - throw new ArgumentException("rightsEnum must have the FlagsAttribute!", nameof(rightsEnum)); - if(Enum.GetUnderlyingType(rightsEnum) != typeof(int)) - throw new ArgumentException("rightsEnum must be an integer type!", nameof(rightsEnum)); - RightsEnum = rightsEnum; - } + /// The value of + public ModelAttribute(RightsType rightsType) => RightsType = rightsType; } } diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index d65202d3c3..4781dff6ec 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// /// Metadata about an installation /// - [Model(typeof(AdministrationRights))] + [Model(RightsType.Administration)] public sealed class Administration { /// diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index 8ce05a3491..f1b6baebeb 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents a BYOND installation /// - [Model(typeof(ByondRights))] + [Model(RightsType.Byond)] public sealed class Byond { /// diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 55056d5c58..b36ee8b8bb 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server /// - [Model(typeof(DreamDaemonRights), CanCrud = true)] + [Model(RightsType.DreamDaemon, CanCrud = true)] public sealed class DreamDaemon { /// diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index 5fcdb8fb51..7b9d561208 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile /// - [Model(typeof(DreamMakerRights), ReadRight = DreamMakerRights.Read, CanCrud = true)] + [Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true)] public sealed class DreamMaker { /// diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index e9ca8c42b7..cbe4334161 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Api.Models /// /// Metadata about a server instance /// - [Model(typeof(InstanceManagerRights), CanCrud = true, CanList = true)] + [Model(RightsType.InstanceManager, CanCrud = true, CanList = true)] public sealed class Instance { /// diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 9ef0173331..19b7557cd7 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -5,14 +5,14 @@ namespace Tgstation.Server.Api.Models /// /// Represents a s permissions in an /// - [Model(typeof(InstanceUserRights), WriteRight = InstanceUserRights.WriteUsers)] + [Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers)] public sealed class InstanceUser { /// - /// See definition in + /// See definition in /// [Permissions(DenyWrite = true)] - public string SystemIdentifier { get; set; } + public long Id { get; set; } /// /// The of the diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 4a36fab2e0..b4b08ece85 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents a git repository /// - [Model(typeof(RepositoryRights), ReadRight = RepositoryRights.Read)] + [Model(RightsType.Repository, ReadRight = RepositoryRights.Read)] public sealed class Repository { /// diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index 7a8e573efa..e970114954 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,12 +1,13 @@ using System; using System.Net; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// /// Represents an access token for the server. Read action generates a new one. Update action expires all for user /// - [Model] + [Model(RightsType.Token, CanList = true)] public sealed class Token { /// diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index 6f8ac34e26..d50666fd50 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -5,14 +5,14 @@ namespace Tgstation.Server.Api.Models /// /// Represents a server /// - [Model(typeof(AdministrationRights), WriteRight = AdministrationRights.EditUsers)] + [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers)] public sealed class User { /// - /// The system identifier for the . On Windows, this is the user's SecurityIdentifier. On UNIX, this is their UID + /// The ID of the /// [Permissions(DenyWrite = true)] - public string SystemIdentifier { get; set; } + public long Id { get; set; } /// /// The name of the diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs new file mode 100644 index 0000000000..b6986341f8 --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Helper for s + /// + public static class RightsHelper + { + /// + /// Map of s to their respective flag s + /// + static readonly IReadOnlyDictionary typeMap = new Dictionary + { + { RightsType.Administration, typeof(AdministrationRights) }, + { RightsType.InstanceManager, typeof(InstanceManagerRights) }, + { RightsType.Token, typeof(TokenRights) }, + + { RightsType.Repository, typeof(RepositoryRights) }, + { RightsType.Byond, typeof(ByondRights) }, + { RightsType.DreamMaker, typeof(DreamMakerRights) }, + { RightsType.DreamDaemon, typeof(DreamDaemonRights) }, + { RightsType.Chat, null }, //TODO + { RightsType.Configuration, null }, //TODO + { RightsType.InstanceUser, typeof(InstanceUserRights) } + }; + + /// + /// Map a given to its respective + /// + /// The to lookup + /// The of the given + public static Type TypeToRight(RightsType rightsType) => typeMap[rightsType]; + } +} diff --git a/src/Tgstation.Server.Api/Rights/RightsType.cs b/src/Tgstation.Server.Api/Rights/RightsType.cs new file mode 100644 index 0000000000..76918983de --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/RightsType.cs @@ -0,0 +1,50 @@ +namespace Tgstation.Server.Api.Rights +{ + /// + /// The type of rights a model uses + /// + public enum RightsType + { + /// + /// + /// + Administration, + /// + /// + /// + InstanceManager, + /// + /// + /// + Token, + + /// + /// + /// + Repository, + /// + /// + /// + Byond, + /// + /// + /// + DreamMaker, + /// + /// + /// + DreamDaemon, + /// + /// TODO + /// + Chat, + /// + /// TODO + /// + Configuration, + /// + /// + /// + InstanceUser + } +} diff --git a/src/Tgstation.Server.Api/RouteHelper.cs b/src/Tgstation.Server.Api/RouteHelper.cs index de9c1f5cf1..1d78cf485c 100644 --- a/src/Tgstation.Server.Api/RouteHelper.cs +++ b/src/Tgstation.Server.Api/RouteHelper.cs @@ -94,5 +94,23 @@ namespace Tgstation.Server.Api /// The to get /// A to the get action public static Route GetJob(Job job) => Read(job.Id); + + /// + /// Get the to a given 's token list + /// + /// The to list tokens for + /// A to the get action + public static Route ListUserTokens(User user) + { + var result = List(null); + result.Path = String.Concat(result.Path, '/', user.Id); + return result; + } + + /// + /// Get the to a server's version + /// + /// + public static Route ServerVersion() => new Route { Path = "/", Method = HttpMethod.Get }; } } diff --git a/tests/Tgstation.Server.Api.Tests/TestModels.cs b/tests/Tgstation.Server.Api.Tests/TestModels.cs new file mode 100644 index 0000000000..c86f7070c6 --- /dev/null +++ b/tests/Tgstation.Server.Api.Tests/TestModels.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Tgstation.Server.Api.Tests +{ + [TestClass] + public sealed class TestModels + { + static IEnumerable> Models() + { + var modelAttributeType = typeof(ModelAttribute); + var targetAssembly = modelAttributeType.Assembly; + foreach(var I in targetAssembly.GetTypes()) + { + var attr = (ModelAttribute)I.GetCustomAttributes(modelAttributeType, false).FirstOrDefault(); + if (attr != default(ModelAttribute)) + yield return new Tuple(I, attr); + } + } + + [TestMethod] + public void TestModelsPropertiesPermissions() + { + var permissionsAttributeType = typeof(PermissionsAttribute); + foreach (var I in Models()) + { + foreach (var J in I.Item1.GetProperties()) + { + var perm = (PermissionsAttribute)J.GetCustomAttributes(permissionsAttributeType, false).FirstOrDefault(); + } + } + } + } +} From 5148d70655f3b7c14d37887d279eee4820402f64 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 14:51:07 -0400 Subject: [PATCH 034/397] Soft stop Administration model --- src/Tgstation.Server.Api/Models/Administration.cs | 6 ++++++ src/Tgstation.Server.Api/Rights/AdministrationRights.cs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 4781dff6ec..63227cef6d 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -21,6 +21,12 @@ namespace Tgstation.Server.Api.Models [Permissions(ReadRight = AdministrationRights.ChangeTelemetry, WriteRight = AdministrationRights.ChangeTelemetry)] public bool EnableTelemetry { get; set; } + /// + /// If the instances will not be stopped when the server exits. Resets to when the server restarts + /// + [Permissions(ReadRight = AdministrationRights.SoftStop, WriteRight = AdministrationRights.SoftStop)] + public bool SoftStop { get; set; } + /// /// Users in the /// diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index da7cb3d559..a9eba8d86e 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -24,5 +24,9 @@ namespace Tgstation.Server.Api.Rights /// User can edit themself and other s /// EditUsers = 4, + /// + /// User can change + /// + SoftStop = 8, } } From 03c2a2401b920fc36240a123bbbef78e1a0902ce Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 14:53:24 -0400 Subject: [PATCH 035/397] Adds Route.Flatten --- src/Tgstation.Server.Api/Route.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Route.cs b/src/Tgstation.Server.Api/Route.cs index 9a29119de5..99789e5443 100644 --- a/src/Tgstation.Server.Api/Route.cs +++ b/src/Tgstation.Server.Api/Route.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using System; +using System.Net.Http; namespace Tgstation.Server.Api { @@ -16,5 +17,12 @@ namespace Tgstation.Server.Api /// The method of the action /// public HttpMethod Method { get; set; } + + /// + /// Adds a portion to + /// + /// The host address + /// A combined and + public Uri Flatten(Uri host) => new Uri(String.Concat(host.ToString().TrimEnd('/'), Path)); } } From 63f53e1bb7cbe2b2e00569dab8774a57179adbce Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 15:32:52 -0400 Subject: [PATCH 036/397] Chat model --- src/Tgstation.Server.Api/Models/Byond.cs | 2 +- src/Tgstation.Server.Api/Models/Chat.cs | 71 +++++++++++++++++++ .../Models/DreamDaemon.cs | 2 +- src/Tgstation.Server.Api/Models/DreamMaker.cs | 2 +- .../Models/InstanceUser.cs | 2 +- src/Tgstation.Server.Api/Models/Repository.cs | 4 +- src/Tgstation.Server.Api/Rights/ChatRights.cs | 40 +++++++++++ .../Tgstation.Server.Api.csproj | 2 + .../Components/IInstanceUserClient.cs | 2 +- .../Components/ITokenClient.cs | 6 +- 10 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Chat.cs create mode 100644 src/Tgstation.Server.Api/Rights/ChatRights.cs diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index f1b6baebeb..7327b27cbd 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents a BYOND installation /// - [Model(RightsType.Byond)] + [Model(RightsType.Byond, RequiresInstance = true)] public sealed class Byond { /// diff --git a/src/Tgstation.Server.Api/Models/Chat.cs b/src/Tgstation.Server.Api/Models/Chat.cs new file mode 100644 index 0000000000..543eae1230 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Chat.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Manage the server chat bots + /// + [Model(RightsType.Chat, RequiresInstance = true)] + public sealed class Chat + { + /// + /// If the IRC client is enabled + /// + [Permissions(WriteRight = ChatRights.SetIrcEnabled)] + public bool IrcEnabled { get; set; } + + /// + /// The IRC server name + /// + [Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)] + public string IrcHost { get; set; } + + /// + /// The IRC server port + /// + [Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)] + public ushort IrcPort { get; set; } + + /// + /// The IRC server NickServ password + /// + [Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)] + public string IrcNickServPassword { get; set; } + + /// + /// Channels the IRC client should join/listen/announce in and allow admin commands + /// + [Permissions(WriteRight = ChatRights.SetIrcChannels)] + public List IrcAdminChannels { get; set; } + + /// + /// Channels the IRC client should join/listen/announce in + /// + [Permissions(WriteRight = ChatRights.SetIrcChannels)] + public List IrcGeneralChannels { get; set; } + + /// + /// If the Discord bot is enabled + /// + [Permissions(WriteRight = ChatRights.SetDiscordEnabled)] + public bool DiscordEnabled { get; set; } + + /// + /// The Discord bot token + /// + [Permissions(ReadRight = ChatRights.SetDiscordSettings, WriteRight = ChatRights.SetDiscordSettings)] + public string DiscordBotToken { get; set; } + + /// + /// Channels the Discord bot should listen/announce in and allow admin commands + /// + [Permissions(WriteRight = ChatRights.SetDiscordChannels)] + public List DiscordAdminChannels { get; set; } + /// + /// Channels the Discord bot should listen/announce in + /// + [Permissions(WriteRight = ChatRights.SetDiscordChannels)] + public List DiscordGeneralChannels { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index b36ee8b8bb..652256ef5b 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server /// - [Model(RightsType.DreamDaemon, CanCrud = true)] + [Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)] public sealed class DreamDaemon { /// diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index 7b9d561208..ca1baaea6d 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile /// - [Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true)] + [Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)] public sealed class DreamMaker { /// diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 19b7557cd7..7218e9e344 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents a s permissions in an /// - [Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers)] + [Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)] public sealed class InstanceUser { /// diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index b4b08ece85..d4411e7d45 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents a git repository /// - [Model(RightsType.Repository, ReadRight = RepositoryRights.Read)] + [Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)] public sealed class Repository { /// @@ -48,10 +48,8 @@ namespace Tgstation.Server.Api.Models /// /// Associated list of GitHub pull request number -> sha for merged pull requests. Adding a value to this list will merge the latest commit of the pull request numbered by the key /// -#pragma warning disable CA2227 // Collection properties should be read only [Permissions(ComplexWrite = true)] public Dictionary PullRequests { get; set; } -#pragma warning restore CA2227 // Collection properties should be read only /// /// The username to access the git repository with diff --git a/src/Tgstation.Server.Api/Rights/ChatRights.cs b/src/Tgstation.Server.Api/Rights/ChatRights.cs new file mode 100644 index 0000000000..b354e6087f --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/ChatRights.cs @@ -0,0 +1,40 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights for + /// + [Flags] + public enum ChatRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User can enable/disable the IRC client + /// + SetIrcEnabled = 1, + /// + /// User can change the IRC settings + /// + SetIrcSettings = 4, + /// + /// User can change the IRC channels + /// + SetIrcChannels = 8, + /// + /// User can enable/disable the Discord bot + /// + SetDiscordEnabled = 16, + /// + /// User can change the Discord settings + /// + SetDiscordSettings = 32, + /// + /// User can change the Discord channels + /// + SetDiscordChannels = 8, + } +} diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index e9c7a313a0..55365cd82f 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -9,10 +9,12 @@ true bin\Release\netstandard2.0\Tgstation.Server.Api.xml + 1701;1702;1705;CA2227 latest + 1701;1702;1705;CA2227 diff --git a/src/Tgstation.Server.Client/Components/IInstanceUserClient.cs b/src/Tgstation.Server.Client/Components/IInstanceUserClient.cs index 68eae0275d..65248e42d5 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceUserClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceUserClient.cs @@ -24,6 +24,6 @@ namespace Tgstation.Server.Client.Components /// The to update /// The for the operation /// A representing the running operation - Task UpdateUser(InstanceUser instanceUser, CancellationToken cancellationToken); + Task Update(InstanceUser instanceUser, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs index a563456217..01b9404b8c 100644 --- a/src/Tgstation.Server.Client/Components/ITokenClient.cs +++ b/src/Tgstation.Server.Client/Components/ITokenClient.cs @@ -33,12 +33,12 @@ namespace Tgstation.Server.Client.Components Task> GetAllInfos(CancellationToken cancellationToken); /// - /// Invalidate the specified active designated by + /// Invalidate the specified active /// - /// The of the to invalidate + /// The to invalidate /// The for the operation /// A representing the running operation - Task Invalidate(long tokenId, CancellationToken cancellationToken); + Task Invalidate(Token token, CancellationToken cancellationToken); /// /// Invalidate all active s for a From 4fd49c1081d10ed7457a9bec41046ceca1d2a4a4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 15:38:09 -0400 Subject: [PATCH 037/397] Last of API model definitions? --- .../Models/InstanceUser.cs | 29 ++++++++++++++----- src/Tgstation.Server.Api/Models/User.cs | 6 ++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 7218e9e344..5fe1cfd480 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -14,19 +14,34 @@ namespace Tgstation.Server.Api.Models [Permissions(DenyWrite = true)] public long Id { get; set; } - /// - /// The of the - /// - public ByondRights ByondRights { get; set; } + /// + /// The of the + /// + public ByondRights ByondRights { get; set; } /// - /// The of the + /// The of the /// public DreamDaemonRights DreamDaemonRights { get; set; } /// - /// The of the + /// The of the + /// + public DreamMakerRights DreamMakerRights { get; set; } + + /// + /// The of the /// public RepositoryRights RepositoryRights { get; set; } - } + + /// + /// The of the + /// + public ChatRights ChatRights { get; set; } + + /// + /// If the may use the remote configuration feature + /// + public bool ConfigurationAccess { get; set; } + } } diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index d50666fd50..fe50e0bc77 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -14,6 +14,12 @@ namespace Tgstation.Server.Api.Models [Permissions(DenyWrite = true)] public long Id { get; set; } + /// + /// The SID/UID of the on Windows/POSIX respectively + /// + [Permissions(DenyWrite = true)] + public string SystemId { get; set; } + /// /// The name of the /// From 2afa64df98ccb6df8c1690cbd42d5c3f6e6d6b89 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 16:02:53 -0400 Subject: [PATCH 038/397] Finish up the configuration stuff --- .../Models/Configuration.cs | 40 +++++++++++++++++++ src/Tgstation.Server.Api/Models/DreamMaker.cs | 6 +++ .../Rights/ConfigurationRights.cs | 28 +++++++++++++ .../Rights/DreamMakerRights.cs | 4 ++ src/Tgstation.Server.Api/RouteHelper.cs | 36 ++++++++++++++++- 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Api/Models/Configuration.cs create mode 100644 src/Tgstation.Server.Api/Rights/ConfigurationRights.cs diff --git a/src/Tgstation.Server.Api/Models/Configuration.cs b/src/Tgstation.Server.Api/Models/Configuration.cs new file mode 100644 index 0000000000..198956a00d --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Configuration.cs @@ -0,0 +1,40 @@ +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a static game file. Create and delete actions uncerimonuously overwrite/delete files + /// + [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] + public sealed class Configuration + { + /// + /// The path to the file + /// + public string Path { get; set; } + + /// + /// If read access to the file was denied + /// + [Permissions(DenyWrite = true)] + public bool ReadDenied { get; set; } + + /// + /// If represents a directory. Will only be if is + /// + [Permissions(DenyWrite = true)] + public bool IsDirectory { get; set; } + + /// + /// The content of the file. Will be if is or during listing operations + /// +#pragma warning disable CA1819 // Properties should not return arrays + public byte[] Content { get; set; } +#pragma warning restore CA1819 // Properties should not return arrays + + /// + /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied + /// + public string LastReadHash { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index ca1baaea6d..e8a093f5a6 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -55,5 +55,11 @@ namespace Tgstation.Server.Api.Models /// [Permissions(WriteRight = DreamMakerRights.SetAutoCompile)] public int? AutoCompileInterval { get; set; } + + /// + /// The .dme file tries to compile with + /// + [Permissions(WriteRight = DreamMakerRights.SetDme)] + public string TargetDme { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs new file mode 100644 index 0000000000..1246644afe --- /dev/null +++ b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs @@ -0,0 +1,28 @@ +using System; + +namespace Tgstation.Server.Api.Rights +{ + /// + /// Rights for + /// + [Flags] + public enum ConfigurationRights + { + /// + /// User has no rights + /// + None = 0, + /// + /// User may read files + /// + Read = 1, + /// + /// User may write files + /// + Write = 2, + /// + /// User may list files + /// + List = 3 + } +} diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index 77dd9b3bae..f770f8c01e 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -28,5 +28,9 @@ namespace Tgstation.Server.Api.Rights /// User may modify /// SetAutoCompile = 8, + /// + /// User may modify + /// + SetDme = 16 } } diff --git a/src/Tgstation.Server.Api/RouteHelper.cs b/src/Tgstation.Server.Api/RouteHelper.cs index 1d78cf485c..b833a3f630 100644 --- a/src/Tgstation.Server.Api/RouteHelper.cs +++ b/src/Tgstation.Server.Api/RouteHelper.cs @@ -99,7 +99,7 @@ namespace Tgstation.Server.Api /// Get the to a given 's token list /// /// The to list tokens for - /// A to the get action + /// A to the list action public static Route ListUserTokens(User user) { var result = List(null); @@ -112,5 +112,39 @@ namespace Tgstation.Server.Api /// /// public static Route ServerVersion() => new Route { Path = "/", Method = HttpMethod.Get }; + + /// + /// Get the to read a file + /// + /// The the file resides in + /// The path to the file in the directory + /// A to the read action + public static Route ReadFile(Instance instance, string path) => new Route { Path = String.Concat("/Configuration/", instance?.Id ?? throw new ArgumentNullException(nameof(instance)), '/', path?.TrimStart('/') ?? throw new ArgumentNullException(nameof(path))), Method = HttpMethod.Get }; + + /// + /// Get the to create a file + /// + /// The the file resides in + /// The path to the file in the directory + /// A to the create action + public static Route CreateFile(Instance instance, string path) + { + var result = ReadFile(instance, path); + result.Method = HttpMethod.Put; + return result; + } + + /// + /// Get the to delete a file + /// + /// The the file resides in + /// The path to the file in the directory + /// A to the delete action + public static Route DeleteFile(Instance instance, string path) + { + var result = ReadFile(instance, path); + result.Method = HttpMethod.Delete; + return result; + } } } From 9ce1229b5dafd69eebe9e0747b71c7751e4c0922 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 16:05:20 -0400 Subject: [PATCH 039/397] Finish up some todos --- src/Tgstation.Server.Api/Models/InstanceUser.cs | 4 ++-- src/Tgstation.Server.Api/Rights/RightsHelper.cs | 4 ++-- src/Tgstation.Server.Api/Rights/RightsType.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 5fe1cfd480..a3e31e1dad 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -40,8 +40,8 @@ namespace Tgstation.Server.Api.Models public ChatRights ChatRights { get; set; } /// - /// If the may use the remote configuration feature + /// The of the /// - public bool ConfigurationAccess { get; set; } + public ConfigurationRights ConfigurationRights { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index b6986341f8..1dcefa440e 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -21,8 +21,8 @@ namespace Tgstation.Server.Api.Rights { RightsType.Byond, typeof(ByondRights) }, { RightsType.DreamMaker, typeof(DreamMakerRights) }, { RightsType.DreamDaemon, typeof(DreamDaemonRights) }, - { RightsType.Chat, null }, //TODO - { RightsType.Configuration, null }, //TODO + { RightsType.Chat, typeof(ChatRights) }, + { RightsType.Configuration, typeof(ConfigurationRights) }, { RightsType.InstanceUser, typeof(InstanceUserRights) } }; diff --git a/src/Tgstation.Server.Api/Rights/RightsType.cs b/src/Tgstation.Server.Api/Rights/RightsType.cs index 76918983de..b2659f8817 100644 --- a/src/Tgstation.Server.Api/Rights/RightsType.cs +++ b/src/Tgstation.Server.Api/Rights/RightsType.cs @@ -35,11 +35,11 @@ /// DreamDaemon, /// - /// TODO + /// /// Chat, /// - /// TODO + /// /// Configuration, /// From 9cc53476c2f4690b9fb197fc64f64cd501fce368 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 16:07:52 -0400 Subject: [PATCH 040/397] Note to self, add unit tests for rights flags --- src/Tgstation.Server.Api/Rights/ChatRights.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Api/Rights/ChatRights.cs b/src/Tgstation.Server.Api/Rights/ChatRights.cs index b354e6087f..30fb36787a 100644 --- a/src/Tgstation.Server.Api/Rights/ChatRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatRights.cs @@ -19,22 +19,22 @@ namespace Tgstation.Server.Api.Rights /// /// User can change the IRC settings /// - SetIrcSettings = 4, + SetIrcSettings = 2, /// /// User can change the IRC channels /// - SetIrcChannels = 8, + SetIrcChannels = 4, /// /// User can enable/disable the Discord bot /// - SetDiscordEnabled = 16, + SetDiscordEnabled = 8, /// /// User can change the Discord settings /// - SetDiscordSettings = 32, + SetDiscordSettings = 16, /// /// User can change the Discord channels /// - SetDiscordChannels = 8, + SetDiscordChannels = 32, } } From a3531de91f259ee3b63fc4512a84b6a2023846d5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 16:29:04 -0400 Subject: [PATCH 041/397] IConfigurationClient --- .../Models/Configuration.cs | 2 +- src/Tgstation.Server.Api/RouteHelper.cs | 13 +++++ .../Components/IConfigurationClient.cs | 55 +++++++++++++++++++ .../Components/IInstanceClient.cs | 5 ++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Client/Components/IConfigurationClient.cs diff --git a/src/Tgstation.Server.Api/Models/Configuration.cs b/src/Tgstation.Server.Api/Models/Configuration.cs index 198956a00d..8497408366 100644 --- a/src/Tgstation.Server.Api/Models/Configuration.cs +++ b/src/Tgstation.Server.Api/Models/Configuration.cs @@ -33,7 +33,7 @@ namespace Tgstation.Server.Api.Models #pragma warning restore CA1819 // Properties should not return arrays /// - /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied + /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 /// public string LastReadHash { get; set; } } diff --git a/src/Tgstation.Server.Api/RouteHelper.cs b/src/Tgstation.Server.Api/RouteHelper.cs index b833a3f630..82592c0654 100644 --- a/src/Tgstation.Server.Api/RouteHelper.cs +++ b/src/Tgstation.Server.Api/RouteHelper.cs @@ -121,6 +121,19 @@ namespace Tgstation.Server.Api /// A to the read action public static Route ReadFile(Instance instance, string path) => new Route { Path = String.Concat("/Configuration/", instance?.Id ?? throw new ArgumentNullException(nameof(instance)), '/', path?.TrimStart('/') ?? throw new ArgumentNullException(nameof(path))), Method = HttpMethod.Get }; + /// + /// Get the to list files for a + /// + /// The the file resides in + /// The directory to list + /// A to the read action + public static Route ListFiles(Instance instance, string directory) + { + var result = ReadFile(instance, directory); + result.Path = String.Concat("/ConfigurationList", result.Path.Substring(result.Path.IndexOf('/', 1))); + return result; + } + /// /// Get the to create a file /// diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs new file mode 100644 index 0000000000..7f7d05a477 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// For managing files + /// + public interface IConfigurationClient : IRightsClient + { + /// + /// List configuration files + /// + /// The path to the directory to list files in + /// The for the operation + /// A of s in the + Task> List(string directory, CancellationToken cancellationToken); + + /// + /// Read a file + /// + /// The file to read + /// The for the operation + /// A representing the running operation + Task Read(Configuration file, CancellationToken cancellationToken); + + /// + /// Overwrite a file with integrity checks + /// + /// The file to write + /// The for the operation + /// A representing the running operation + Task Write(Configuration file, CancellationToken cancellationToken); + + /// + /// Create/overwrite a file + /// + /// The file to write + /// The for the operation + /// A representing the running operation + Task Create(Configuration file, CancellationToken cancellationToken); + + /// + /// Delete a file + /// + /// The file to delete + /// The for the operation + /// A representing the running operation + Task Delete(Configuration file, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 0e891f674f..12f2eda8c1 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -24,6 +24,11 @@ namespace Tgstation.Server.Client.Components /// IDreamDaemonClient DreamDaemon { get; } + /// + /// Access the + /// + IConfigurationClient Configuration { get; } + /// /// Access the /// From 1833623aa03581b3c6f99409bf7946c901923d09 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 16:33:48 -0400 Subject: [PATCH 042/397] IAdministrationClient --- .../Components/IAdministrationClient.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Tgstation.Server.Client/Components/IAdministrationClient.cs diff --git a/src/Tgstation.Server.Client/Components/IAdministrationClient.cs b/src/Tgstation.Server.Client/Components/IAdministrationClient.cs new file mode 100644 index 0000000000..0931621521 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IAdministrationClient.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// For managing server administration + /// + public interface IAdministrationClient : IRightsClient + { + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); + + /// + /// Updates the setttings + /// + /// The to update + /// The for the operation + /// A representing the running operation + Task Update(Administration administration, CancellationToken cancellationToken); + } +} From 72458814532b50f69f107eed17366ab64adffb90 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 16:51:45 -0400 Subject: [PATCH 043/397] Remove PermissionAttribute.ComplexWrite --- src/Tgstation.Server.Api/Models/Configuration.cs | 2 ++ src/Tgstation.Server.Api/Models/Instance.cs | 2 +- src/Tgstation.Server.Api/Models/Repository.cs | 6 +++--- src/Tgstation.Server.Api/PermissionsAttribute.cs | 5 ----- .../Rights/InstanceManagerRights.cs | 10 +++------- src/Tgstation.Server.Api/Rights/RepositoryRights.cs | 8 ++------ 6 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Configuration.cs b/src/Tgstation.Server.Api/Models/Configuration.cs index 8497408366..4b750c9e6a 100644 --- a/src/Tgstation.Server.Api/Models/Configuration.cs +++ b/src/Tgstation.Server.Api/Models/Configuration.cs @@ -5,8 +5,10 @@ namespace Tgstation.Server.Api.Models /// /// Represents a static game file. Create and delete actions uncerimonuously overwrite/delete files /// +#pragma warning disable CA1724 // System.Configuration name conflict [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] public sealed class Configuration +#pragma warning restore CA1724 // System.Configuration name conflict { /// /// The path to the file diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index cbe4334161..43d3eea404 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Models /// /// If the is online /// - [Permissions(ComplexWrite = true)] + [Permissions(WriteRight = InstanceManagerRights.SetOnline)] public bool Online { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index d4411e7d45..57c1a14f7d 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Api.Models /// /// The origin URL. If , the does not exist /// - [Permissions(ComplexWrite = true)] + [Permissions(WriteRight = RepositoryRights.SetOrigin)] public string Origin { get; set; } /// @@ -24,7 +24,7 @@ namespace Tgstation.Server.Api.Models /// /// The branch or tag HEAD points to /// - [Permissions(ComplexWrite = true)] + [Permissions(WriteRight = RepositoryRights.SetReference)] public string Reference { get; set; } /// @@ -48,7 +48,7 @@ namespace Tgstation.Server.Api.Models /// /// Associated list of GitHub pull request number -> sha for merged pull requests. Adding a value to this list will merge the latest commit of the pull request numbered by the key /// - [Permissions(ComplexWrite = true)] + [Permissions(WriteRight = RepositoryRights.MergePullRequest)] public Dictionary PullRequests { get; set; } /// diff --git a/src/Tgstation.Server.Api/PermissionsAttribute.cs b/src/Tgstation.Server.Api/PermissionsAttribute.cs index fd35005dcb..7d4962758f 100644 --- a/src/Tgstation.Server.Api/PermissionsAttribute.cs +++ b/src/Tgstation.Server.Api/PermissionsAttribute.cs @@ -22,10 +22,5 @@ namespace Tgstation.Server.Api /// If the field cannot be written to /// public bool DenyWrite { get; set; } - - /// - /// If the field has multiple write permissions - /// - public bool ComplexWrite { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index 31a7eea0d3..a00d4ffedc 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -31,18 +31,14 @@ namespace Tgstation.Server.Api.Rights /// /// User can online s they can view /// - Online = 16, - /// - /// User can offline s they can view - /// - Offline = 32, + SetOnline = 16, /// /// User can delete s they can view /// - Delete = 64, + Delete = 32, /// /// User can view all s /// - List = 128 + List = 64 } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index a17ab1c6f8..80339d9d7f 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -45,16 +45,12 @@ namespace Tgstation.Server.Api.Rights /// ChangeCredentials = 256, /// - /// User may reset the 's HEAD to the origin reference of whatever branch it is tracking + /// User may set to another git reference (not a SHA) /// - Reset = 512, + SetReference = 512, /// /// User may read all fields in the with the exception of /// Read = 1024, - /// - /// User may set to a backup tag - /// - CheckoutBackup = 2048, } } From 5c7ff608295bdbfeccff64f5714947d75177399f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 16:58:32 -0400 Subject: [PATCH 044/397] Chat client --- .../Components/IAdministrationClient.cs | 3 +- .../Components/IChatClient.cs | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 src/Tgstation.Server.Client/Components/IChatClient.cs diff --git a/src/Tgstation.Server.Client/Components/IAdministrationClient.cs b/src/Tgstation.Server.Client/Components/IAdministrationClient.cs index 0931621521..a68aeb5cfe 100644 --- a/src/Tgstation.Server.Client/Components/IAdministrationClient.cs +++ b/src/Tgstation.Server.Client/Components/IAdministrationClient.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; diff --git a/src/Tgstation.Server.Client/Components/IChatClient.cs b/src/Tgstation.Server.Client/Components/IChatClient.cs new file mode 100644 index 0000000000..47cc7ce057 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IChatClient.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// For managing server administration + /// + public interface IChatClient : IRightsClient + { + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); + + /// + /// Updates the setttings + /// + /// The to update + /// The for the operation + /// A representing the running operation + Task Update(Chat chat, CancellationToken cancellationToken); + } +} From 9ff800cdfe663f629fe8766db669858b81d9761b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Apr 2018 17:18:49 -0400 Subject: [PATCH 045/397] Dreammaker and jobs client --- src/Tgstation.Server.Api/Models/Job.cs | 7 +++- .../Components/IChatClient.cs | 4 +-- .../Components/IDreamMakerClient.cs | 35 +++++++++++++++++++ .../Components/IInstanceClient.cs | 30 ++++++++++------ .../Components/IJobsClient.cs | 28 +++++++++++++++ .../Components/ITokenClient.cs | 2 +- src/Tgstation.Server.Client/IServerClient.cs | 10 ++++++ 7 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 src/Tgstation.Server.Client/Components/IDreamMakerClient.cs create mode 100644 src/Tgstation.Server.Client/Components/IJobsClient.cs diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs index 2aa7ac6c55..d0231963b4 100644 --- a/src/Tgstation.Server.Api/Models/Job.cs +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Api.Models { /// - /// Represents a long running job on the server + /// Represents a long running job on the server. Model is read-only, updates attempt to cancel the job /// [Model] public sealed class Job @@ -37,5 +37,10 @@ namespace Tgstation.Server.Api.Models /// If the has incremental progress, this will range from 1 - 100. 0 otherwise /// public int Progress { get; set; } + + /// + /// If the current user has permission to cancel the job. Will be if isn't + /// + public bool? UserCanCancel { get; set; } } } diff --git a/src/Tgstation.Server.Client/Components/IChatClient.cs b/src/Tgstation.Server.Client/Components/IChatClient.cs index 47cc7ce057..4699395a6f 100644 --- a/src/Tgstation.Server.Client/Components/IChatClient.cs +++ b/src/Tgstation.Server.Client/Components/IChatClient.cs @@ -6,7 +6,7 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { /// - /// For managing server administration + /// For managing the chat bots /// public interface IChatClient : IRightsClient { @@ -14,7 +14,7 @@ namespace Tgstation.Server.Client.Components /// Get the represented by the /// /// The for the operation - /// A resulting in the represented by the + /// A resulting in the represented by the Task Read(CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs b/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs new file mode 100644 index 0000000000..276f1d2258 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs @@ -0,0 +1,35 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Client.Components +{ + /// + /// For managing the compiler + /// + public interface IDreamMakerClient : IRightsClient + { + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); + + /// + /// Updates the setttings + /// + /// The to update + /// The for the operation + /// A representing the running operation + Task Update(DreamMaker dreamMaker, CancellationToken cancellationToken); + + /// + /// Compile the current repository revision + /// + /// The for the operation + /// A representing the running operation + Task Compile(CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index 12f2eda8c1..baebd33d91 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -29,16 +29,26 @@ namespace Tgstation.Server.Client.Components /// IConfigurationClient Configuration { get; } - /// - /// Access the - /// - IInstanceUserClient Users { get; } + /// + /// Access the + /// + IInstanceUserClient Users { get; } - /// - /// Get the represented by the - /// - /// The for the operation - /// A resulting in the represented by the - Task Read(CancellationToken cancellationToken); + /// + /// Access the + /// + IChatClient Chat { get; } + + /// + /// Access the + /// + IDreamMakerClient DreamMaker { get; } + + /// + /// Get the represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Client/Components/IJobsClient.cs b/src/Tgstation.Server.Client/Components/IJobsClient.cs new file mode 100644 index 0000000000..7f912b3ab1 --- /dev/null +++ b/src/Tgstation.Server.Client/Components/IJobsClient.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Client.Components +{ + /// + /// Access to running jobs + /// + public interface IJobsClient + { + /// + /// List the active jobs the user can view + /// + /// The for the operation + /// A resulting in a of active s the user can view + Task> List(CancellationToken cancellationToken); + + /// + /// Cancels a + /// + /// The to cancel + /// The for the operation + /// A representing the running operation + Task Cancel(Job job, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs index 01b9404b8c..03965132a0 100644 --- a/src/Tgstation.Server.Client/Components/ITokenClient.cs +++ b/src/Tgstation.Server.Client/Components/ITokenClient.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Client.Components public interface ITokenClient: IRightsClient { /// - /// Generate a new for the connected user, expiring the current one if applicable + /// Generate a new for the connected user/ipaddress, deleting the current one if applicable /// /// The for the operation /// A resulting in the new diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index cd0b17e357..aa0fb7cc39 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -40,6 +40,16 @@ namespace Tgstation.Server.Client /// IInstanceManagerClient Instances { get; } + /// + /// Access the + /// + IAdministrationClient Administration { get; } + + /// + /// These generally shouldn't be used in favor of the based polling and s other clients use. However, this is the only way to access jobs the client didn't start in it's current session + /// + IJobsClient Jobs { get; } + /// /// The of the connected server /// From 196005f3a6956f35e9414514264270a30cea4f9c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 4 Apr 2018 18:40:08 -0400 Subject: [PATCH 046/397] Fix appveyor only logging one test set --- appveyor.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index f51af44eb6..5d25f73df1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -32,11 +32,21 @@ test_script: - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path% - copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Api.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Api.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Client.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Client.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.CommandLine.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.CommandLine.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.ControlPanel.Tests\bin\%CONFIGURATION%\Tgstation.Server.ControlPanel.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) From 15dc6bb2de2a696e425c2223d38221138c129de8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 4 Apr 2018 20:29:48 -0400 Subject: [PATCH 047/397] Implement some server and console stuff --- src/Tgstation.Server.Host.Console/Program.cs | 17 ++++++++++++----- src/Tgstation.Server.Host/AssemblyInfo.cs | 3 +++ src/Tgstation.Server.Host/IServer.cs | 18 ++++++++++++++++++ src/Tgstation.Server.Host/IServerFactory.cs | 14 ++++++++++++++ src/Tgstation.Server.Host/Server.cs | 15 +++++++++++++++ src/Tgstation.Server.Host/ServerFactory.cs | 9 +++++++++ .../Tgstation.Server.Api.Tests.csproj | 2 +- .../Tgstation.Server.Client.Tests.csproj | 2 +- .../Tgstation.Server.CommandLine.Tests.csproj | 2 +- .../TestProgram.cs | 8 ++++++++ .../Tgstation.Server.Host.Console.Tests.csproj | 3 ++- .../Tgstation.Server.Host.Tests/TestServer.cs | 16 ++++++++++++++++ .../Tgstation.Server.Host.Tests.csproj | 2 +- 13 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 src/Tgstation.Server.Host/AssemblyInfo.cs create mode 100644 src/Tgstation.Server.Host/IServer.cs create mode 100644 src/Tgstation.Server.Host/IServerFactory.cs create mode 100644 src/Tgstation.Server.Host/Server.cs create mode 100644 src/Tgstation.Server.Host/ServerFactory.cs create mode 100644 tests/Tgstation.Server.Host.Tests/TestServer.cs diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index 0c8a1dc557..cf46d17f49 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -1,20 +1,27 @@ -using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Host.Console { /// /// Contains the entrypoint for the application /// - public static class Program + static class Program { + /// + /// The for the + /// + internal static IServerFactory ServerFactory { get; set; } = new ServerFactory(); + /// /// Entrypoint for the application /// /// A representing the running operation - public static Task Main() + internal static async Task Main() { - System.Console.WriteLine("Hello world!"); - return Task.CompletedTask; + using (var server = ServerFactory.CreateServer()) + await server.RunAsync().ConfigureAwait(false); } } } diff --git a/src/Tgstation.Server.Host/AssemblyInfo.cs b/src/Tgstation.Server.Host/AssemblyInfo.cs new file mode 100644 index 0000000000..d2a28919a4 --- /dev/null +++ b/src/Tgstation.Server.Host/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Tgstation.Server.Host.Tests")] diff --git a/src/Tgstation.Server.Host/IServer.cs b/src/Tgstation.Server.Host/IServer.cs new file mode 100644 index 0000000000..722f2f9e98 --- /dev/null +++ b/src/Tgstation.Server.Host/IServer.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host +{ + /// + /// Represents the host + /// + public interface IServer : IDisposable + { + /// + /// Runs the + /// + /// A representing the running operation + Task RunAsync(); + } +} diff --git a/src/Tgstation.Server.Host/IServerFactory.cs b/src/Tgstation.Server.Host/IServerFactory.cs new file mode 100644 index 0000000000..7a5efbc7b2 --- /dev/null +++ b/src/Tgstation.Server.Host/IServerFactory.cs @@ -0,0 +1,14 @@ +namespace Tgstation.Server.Host +{ + /// + /// For creating s + /// + public interface IServerFactory + { + /// + /// Create a + /// + /// A new + IServer CreateServer(); + } +} diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs new file mode 100644 index 0000000000..da98592c85 --- /dev/null +++ b/src/Tgstation.Server.Host/Server.cs @@ -0,0 +1,15 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host +{ + /// + sealed class Server : IServer + { + /// + public void Dispose() { } + + /// + public Task RunAsync() => Task.CompletedTask; + } +} diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs new file mode 100644 index 0000000000..d22e9938ee --- /dev/null +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -0,0 +1,9 @@ +namespace Tgstation.Server.Host +{ + /// + public sealed class ServerFactory : IServerFactory + { + /// + public IServer CreateServer() => new Server(); + } +} diff --git a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj index 103f4472b1..e4e77805e2 100644 --- a/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj +++ b/tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj index 8061f3b7e8..caacb5743a 100644 --- a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj +++ b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj b/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj index 46bbd02d4a..43a487525f 100644 --- a/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj +++ b/tests/Tgstation.Server.CommandLine.Tests/Tgstation.Server.CommandLine.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs index 9659a8dd16..1d089f6316 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs +++ b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using System.Threading.Tasks; namespace Tgstation.Server.Host.Console.Tests @@ -9,7 +10,14 @@ namespace Tgstation.Server.Host.Console.Tests [TestMethod] public async Task TestProgramRuns() { + var mockServer = new Mock(); + mockServer.Setup(x => x.RunAsync()).Returns(Task.CompletedTask).Verifiable(); + var mockServerFactory = new Mock(); + mockServerFactory.Setup(x => x.CreateServer()).Returns(mockServer.Object).Verifiable(); + Program.ServerFactory = mockServerFactory.Object; await Program.Main().ConfigureAwait(false); + mockServer.VerifyAll(); + mockServerFactory.VerifyAll(); } } } diff --git a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj index 1234f45518..2e6e921228 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj +++ b/tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj @@ -8,7 +8,8 @@ - + + diff --git a/tests/Tgstation.Server.Host.Tests/TestServer.cs b/tests/Tgstation.Server.Host.Tests/TestServer.cs new file mode 100644 index 0000000000..4abee0c0a8 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/TestServer.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Tgstation.Server.Host.Tests +{ + [TestClass] + public sealed class TestServer + { + [TestMethod] + public async Task TestRunAndDispose() + { + using (var server = new Server()) + await server.RunAsync().ConfigureAwait(false); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj index 4f0518d744..f637b93ead 100644 --- a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj +++ b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj @@ -8,7 +8,7 @@ - + From e7548f0205261625003d3c9e45ccfba876947abd Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 4 Apr 2018 20:43:13 -0400 Subject: [PATCH 048/397] Write the service runner, remove the tests because the ServiceBase pattern can't achieve 100% code coverage --- appveyor.yml | 3 - src/Tgstation.Server.Host.Console/Program.cs | 5 +- src/Tgstation.Server.Host.Service/Program.cs | 8 +-- .../ServerService.cs | 60 ++++++++++++++++ .../Tgstation.Server.Host.Service.csproj | 7 ++ src/Tgstation.Server.Host/IServer.cs | 4 +- src/Tgstation.Server.Host/Server.cs | 2 +- .../TestProgram.cs | 5 +- .../Properties/AssemblyInfo.cs | 20 ------ ...Tgstation.Server.Host.Service.Tests.csproj | 72 ------------------- .../packages.config | 5 -- .../Tgstation.Server.Host.Tests/TestServer.cs | 2 +- tgstation-server.sln | 6 -- 13 files changed, 82 insertions(+), 117 deletions(-) create mode 100644 src/Tgstation.Server.Host.Service/ServerService.cs delete mode 100644 tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs delete mode 100644 tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj delete mode 100644 tests/Tgstation.Server.Host.Service.Tests/packages.config diff --git a/appveyor.yml b/appveyor.yml index 5d25f73df1..2330c6d64c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -44,9 +44,6 @@ test_script: - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - ps: $wc = New-Object 'System.Net.WebClient' - - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.ControlPanel.Tests\bin\%CONFIGURATION%\Tgstation.Server.ControlPanel.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index cf46d17f49..9f80e63d95 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -17,11 +17,12 @@ namespace Tgstation.Server.Host.Console /// /// Entrypoint for the application /// + /// The arguments for the /// A representing the running operation - internal static async Task Main() + internal static async Task Main(string[] args) { using (var server = ServerFactory.CreateServer()) - await server.RunAsync().ConfigureAwait(false); + await server.RunAsync(args, default).ConfigureAwait(false); } } } diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 880b0a08b6..6b3d490082 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Host.Service +using System.ServiceProcess; + +namespace Tgstation.Server.Host.Service { /// /// Contains the entrypoint for the application @@ -8,8 +10,6 @@ /// /// Entrypoint for the application /// - static void Main() - { - } + static void Main() => ServiceBase.Run(new ServerService(new ServerFactory())); } } diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs new file mode 100644 index 0000000000..8815faa3da --- /dev/null +++ b/src/Tgstation.Server.Host.Service/ServerService.cs @@ -0,0 +1,60 @@ +using System.ServiceProcess; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Service +{ + /// + /// Represents a as a + /// + sealed class ServerService : ServiceBase + { + /// + /// The for the + /// + IServer server; + + /// + /// The recieved from of + /// + Task serverTask; + + /// + /// The for the + /// + CancellationTokenSource cancellationTokenSource; + + /// + /// Construct a + /// + /// The to create with + public ServerService(IServerFactory serverFactory) + { + ServiceName = "tgstation-server"; + server = serverFactory.CreateServer(); + } + + /// + protected override void Dispose(bool disposing) + { + server.Dispose(); + cancellationTokenSource.Dispose(); + base.Dispose(disposing); + } + + /// + protected override void OnStart(string[] args) + { + cancellationTokenSource?.Dispose(); + cancellationTokenSource = new CancellationTokenSource(); + serverTask = server.RunAsync(args, cancellationTokenSource.Token); + } + + /// + protected override void OnStop() + { + cancellationTokenSource.Cancel(); + serverTask.GetAwaiter().GetResult(); + } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index 4072e3c73b..09f65920ba 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -61,6 +61,9 @@ + + Component + @@ -80,5 +83,9 @@ Tgstation.Server.Host + + + + \ No newline at end of file diff --git a/src/Tgstation.Server.Host/IServer.cs b/src/Tgstation.Server.Host/IServer.cs index 722f2f9e98..83b6b9bc1b 100644 --- a/src/Tgstation.Server.Host/IServer.cs +++ b/src/Tgstation.Server.Host/IServer.cs @@ -12,7 +12,9 @@ namespace Tgstation.Server.Host /// /// Runs the /// + /// The arguments for the + /// The for the operation /// A representing the running operation - Task RunAsync(); + Task RunAsync(string[] args, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index da98592c85..02cc380729 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -10,6 +10,6 @@ namespace Tgstation.Server.Host public void Dispose() { } /// - public Task RunAsync() => Task.CompletedTask; + public Task RunAsync(string[] args, CancellationToken cancellationToken) => Task.CompletedTask; } } diff --git a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs index 1d089f6316..103b66c410 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs +++ b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Console.Tests @@ -11,11 +12,11 @@ namespace Tgstation.Server.Host.Console.Tests public async Task TestProgramRuns() { var mockServer = new Mock(); - mockServer.Setup(x => x.RunAsync()).Returns(Task.CompletedTask).Verifiable(); + mockServer.Setup(x => x.RunAsync(null, It.IsAny())).Returns(Task.CompletedTask).Verifiable(); var mockServerFactory = new Mock(); mockServerFactory.Setup(x => x.CreateServer()).Returns(mockServer.Object).Verifiable(); Program.ServerFactory = mockServerFactory.Object; - await Program.Main().ConfigureAwait(false); + await Program.Main(null).ConfigureAwait(false); mockServer.VerifyAll(); mockServerFactory.VerifyAll(); } diff --git a/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs b/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index e7c6fc8a51..0000000000 --- a/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Tgstation.Server.Host.Service.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Tgstation.Server.Host.Service.Tests")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] - -[assembly: Guid("f0cd3f14-e9c0-4477-99f7-f1e22cb8d85e")] - -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj deleted file mode 100644 index 3741d9e073..0000000000 --- a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - - - Debug - AnyCPU - {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E} - Library - Properties - Tgstation.Server.Host.Service.Tests - Tgstation.Server.Host.Service.Tests - v4.7.1 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - latest - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - latest - - - - ..\..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - - - - - - - - - {29927416-3b78-49a7-a560-5ccaa638b6b4} - Tgstation.Server.Host.Service - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Service.Tests/packages.config b/tests/Tgstation.Server.Host.Service.Tests/packages.config deleted file mode 100644 index 8819694ccc..0000000000 --- a/tests/Tgstation.Server.Host.Service.Tests/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Tests/TestServer.cs b/tests/Tgstation.Server.Host.Tests/TestServer.cs index 4abee0c0a8..3263577584 100644 --- a/tests/Tgstation.Server.Host.Tests/TestServer.cs +++ b/tests/Tgstation.Server.Host.Tests/TestServer.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Tests public async Task TestRunAndDispose() { using (var server = new Server()) - await server.RunAsync().ConfigureAwait(false); + await server.RunAsync(null, default).ConfigureAwait(false); } } } diff --git a/tgstation-server.sln b/tgstation-server.sln index 9c2f476263..2f9101cfbc 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -40,8 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Client", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.ControlPanel.Tests", "tests\Tgstation.Server.ControlPanel.Tests\Tgstation.Server.ControlPanel.Tests.csproj", "{93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Service.Tests", "tests\Tgstation.Server.Host.Service.Tests\Tgstation.Server.Host.Service.Tests.csproj", "{F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Api.Tests", "tests\Tgstation.Server.Api.Tests\Tgstation.Server.Api.Tests.csproj", "{BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Client.Tests", "tests\Tgstation.Server.Client.Tests\Tgstation.Server.Client.Tests.csproj", "{E5301AF1-4F74-4982-BF24-95F23CC5D5B2}" @@ -94,10 +92,6 @@ Global {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Debug|Any CPU.Build.0 = Debug|Any CPU {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Release|Any CPU.Build.0 = Release|Any CPU - {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F0CD3F14-E9C0-4477-99F7-F1E22CB8D85E}.Release|Any CPU.Build.0 = Release|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Release|Any CPU.ActiveCfg = Release|Any CPU From 9233d4d1054a9cb9b2e9a33d7a650ccd669438c9 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Apr 2018 11:02:45 -0400 Subject: [PATCH 049/397] Add auto update to admin model --- .../Models/Administration.cs | 21 ++++++++++++++++++- .../Rights/AdministrationRights.cs | 9 ++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 63227cef6d..7fd8bdfc84 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models @@ -27,6 +28,24 @@ namespace Tgstation.Server.Api.Models [Permissions(ReadRight = AdministrationRights.SoftStop, WriteRight = AdministrationRights.SoftStop)] public bool SoftStop { get; set; } + /// + /// The git repository to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to upstream pulls will be disabled entirely + /// + [Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)] + public string UpstreamRepository { get; set; } + + /// + /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is higher than 's the update cannot be applied due to API changes + /// + [Permissions(DenyWrite = true)] + public Version LatestVersion { get; set; } + + /// + /// Changes the version of Tgstation.Server.Host to the given version from the upstream repository + /// + [Permissions(WriteRight = AdministrationRights.ChangeVersion)] + public Version CurrentVersion { get; set; } + /// /// Users in the /// diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index a9eba8d86e..5a9a37aaf8 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -28,5 +28,14 @@ namespace Tgstation.Server.Api.Rights /// User can change /// SoftStop = 8, + /// + /// User can change + /// + ChangeVersion = 16, + /// + /// User can change + /// + SetUpstreamRepository + } } From 1c1043926d78b6af5eee8641cd7cfe26a638ea1f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Apr 2018 11:37:55 -0400 Subject: [PATCH 050/397] Add skeleton Watchdog project --- .../Tgstation.Server.Host.Watchdog.csproj | 23 ++++++++++++++ ...gstation.Server.Host.Watchdog.Tests.csproj | 30 +++++++++++++++++++ tgstation-server.sln | 14 ++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj create mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj new file mode 100644 index 0000000000..d90f320a9d --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + + + + latest + true + + bin\Release\netstandard2.0\Tgstation.Server.Host.Watchdog.xml + + + + latest + + + + + + + + diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj new file mode 100644 index 0000000000..e2e2dcaebc --- /dev/null +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj @@ -0,0 +1,30 @@ + + + + netcoreapp2.0 + + false + + + + latest + true + + bin\Release\netcoreapp2.0\Tgstation.Server.Host.Watchdog.Tests.xml + + + + latest + + + + + + + + + + + + + diff --git a/tgstation-server.sln b/tgstation-server.sln index 2f9101cfbc..071f4c77f5 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27428.1 +VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DA32568D-1D8D-4A4C-9943-BFD3CE796B3F}" ProjectSection(SolutionItems) = preProject @@ -50,6 +50,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Tests EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Watchdog", "src\Tgstation.Server.Host.Watchdog\Tgstation.Server.Host.Watchdog.csproj", "{5D2D682C-6BF0-439C-850B-6AB945BBEAEA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Watchdog.Tests", "tests\Tgstation.Server.Host.Watchdog.Tests\Tgstation.Server.Host.Watchdog.Tests.csproj", "{7500F776-4384-4B5F-A8D8-22461CAD108B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -112,6 +116,14 @@ Global {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Release|Any CPU.Build.0 = Release|Any CPU + {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Release|Any CPU.Build.0 = Release|Any CPU + {7500F776-4384-4B5F-A8D8-22461CAD108B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7500F776-4384-4B5F-A8D8-22461CAD108B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 9415ef722a63889f392b45e8722586c2783f003a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Apr 2018 12:46:57 -0400 Subject: [PATCH 051/397] Make things reference the watchdog instead of the host directly --- src/Tgstation.Server.Host.Console/Program.cs | 23 +++----- .../Tgstation.Server.Host.Console.csproj | 2 +- src/Tgstation.Server.Host.Service/Program.cs | 3 +- .../ServerService.cs | 24 ++++---- .../Tgstation.Server.Host.Service.csproj | 12 ++-- .../IWatchdog.cs | 20 +++++++ .../IWatchdogFactory.cs | 14 +++++ .../Watchdog.cs | 58 +++++++++++++++++++ .../WatchdogFactory.cs | 9 +++ src/Tgstation.Server.Host/IServer.cs | 5 ++ src/Tgstation.Server.Host/Server.cs | 9 ++- .../TestProgram.cs | 9 +-- 12 files changed, 147 insertions(+), 41 deletions(-) create mode 100644 src/Tgstation.Server.Host.Watchdog/IWatchdog.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/Watchdog.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index 9f80e63d95..8beea9b3f4 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -1,6 +1,5 @@ -using Microsoft.AspNetCore.Hosting; -using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Console { @@ -12,17 +11,13 @@ namespace Tgstation.Server.Host.Console /// /// The for the /// - internal static IServerFactory ServerFactory { get; set; } = new ServerFactory(); + internal static IWatchdogFactory WatchdogFactory { get; set; } = new WatchdogFactory(); - /// - /// Entrypoint for the application - /// - /// The arguments for the - /// A representing the running operation - internal static async Task Main(string[] args) - { - using (var server = ServerFactory.CreateServer()) - await server.RunAsync(args, default).ConfigureAwait(false); - } + /// + /// Entrypoint for the application + /// + /// The arguments for the + /// A representing the running operation + internal static Task Main(string[] args) => WatchdogFactory.CreateWatchdog().RunAsync(args, default); } } diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index bbedcfffef..536fe76b0a 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 6b3d490082..53773df933 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -1,4 +1,5 @@ using System.ServiceProcess; +using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Service { @@ -10,6 +11,6 @@ namespace Tgstation.Server.Host.Service /// /// Entrypoint for the application /// - static void Main() => ServiceBase.Run(new ServerService(new ServerFactory())); + static void Main() => ServiceBase.Run(new ServerService(new WatchdogFactory())); } } diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs index 8815faa3da..5cd218e587 100644 --- a/src/Tgstation.Server.Host.Service/ServerService.cs +++ b/src/Tgstation.Server.Host.Service/ServerService.cs @@ -1,6 +1,7 @@ using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Service { @@ -10,34 +11,33 @@ namespace Tgstation.Server.Host.Service sealed class ServerService : ServiceBase { /// - /// The for the + /// The for the /// - IServer server; + IWatchdog watchdog; /// /// The recieved from of /// - Task serverTask; + Task watchdogTask; /// /// The for the /// CancellationTokenSource cancellationTokenSource; - /// - /// Construct a - /// - /// The to create with - public ServerService(IServerFactory serverFactory) + /// + /// Construct a + /// + /// The to create with + public ServerService(IWatchdogFactory watchdogFactory) { ServiceName = "tgstation-server"; - server = serverFactory.CreateServer(); + watchdog = watchdogFactory.CreateWatchdog(); } /// protected override void Dispose(bool disposing) { - server.Dispose(); cancellationTokenSource.Dispose(); base.Dispose(disposing); } @@ -47,14 +47,14 @@ namespace Tgstation.Server.Host.Service { cancellationTokenSource?.Dispose(); cancellationTokenSource = new CancellationTokenSource(); - serverTask = server.RunAsync(args, cancellationTokenSource.Token); + watchdogTask = watchdog.RunAsync(args, cancellationTokenSource.Token); } /// protected override void OnStop() { cancellationTokenSource.Cancel(); - serverTask.GetAwaiter().GetResult(); + watchdogTask.GetAwaiter().GetResult(); } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index 09f65920ba..530e071d08 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -77,15 +77,15 @@ false - - - {2b69ad6d-2b5a-4023-8ead-0bd1b18e028a} - Tgstation.Server.Host - - + + + {5d2d682c-6bf0-439c-850b-6ab945bbeaea} + Tgstation.Server.Host.Watchdog + + \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs new file mode 100644 index 0000000000..3736377287 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// The watchdog for a + /// + public interface IWatchdog + { + /// + /// Run the + /// + /// The arguments for the + /// The for the operation + /// A representing the running operation + Task RunAsync(string[] args, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs new file mode 100644 index 0000000000..a1b203761a --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs @@ -0,0 +1,14 @@ +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// Factory for creating s + /// + public interface IWatchdogFactory + { + /// + /// Create a + /// + /// A new + IWatchdog CreateWatchdog(); + } +} diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs new file mode 100644 index 0000000000..f655debe8b --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + sealed class Watchdog : MarshalByRefObject, IWatchdog + { + string DomainName => GetType().Namespace.Replace(nameof(Watchdog), String.Empty); + + string DllName => String.Concat(DomainName, ".dll"); + + async Task RunServer(string[] args, CancellationToken cancellationToken) + { + var assembly = Assembly.LoadFrom(DllName); + //the only thing we need to reflect is the IServerFactory, we can cross reference everything else from there + var factoryInterfaceType = assembly.GetType(String.Concat(DomainName, ".IServerFactory")); + var factoryType = assembly.GetTypes().Where(x => factoryInterfaceType.IsAssignableFrom(x)).First(); + var factory = Activator.CreateInstance(factoryType); + var factoryFunction = factoryInterfaceType.GetMethods().First(); + var serverType = factoryFunction.ReturnType; + var serverRunFunction = serverType.GetMethods().First(); + var serverUpdatePathAccessor = serverType.GetProperties().First().GetAccessors().First(); + + var server = (IDisposable)factoryFunction.Invoke(factory, Array.Empty()); + using (server) + { + var task = (Task)serverRunFunction.Invoke(server, new object[] { args, cancellationToken }); + await task.ConfigureAwait(false); + return (string)serverUpdatePathAccessor.Invoke(server, Array.Empty()); + } + } + + /// + public async Task RunAsync(string[] args, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + string updatePath = null; + + await Task.CompletedTask.ConfigureAwait(false); + + if (updatePath == null) + break; + + //Ensure the assembly is unloaded + GC.Collect(Int32.MaxValue, GCCollectionMode.Default, true); + + File.Delete(DllName); + File.Move(updatePath, DllName); + } + } + } +} diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs new file mode 100644 index 0000000000..d1dab42db5 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -0,0 +1,9 @@ +namespace Tgstation.Server.Host.Watchdog +{ + /// + public sealed class WatchdogFactory : IWatchdogFactory + { + /// + public IWatchdog CreateWatchdog() => new Watchdog(); + } +} diff --git a/src/Tgstation.Server.Host/IServer.cs b/src/Tgstation.Server.Host/IServer.cs index 83b6b9bc1b..4db6422790 100644 --- a/src/Tgstation.Server.Host/IServer.cs +++ b/src/Tgstation.Server.Host/IServer.cs @@ -9,6 +9,11 @@ namespace Tgstation.Server.Host /// public interface IServer : IDisposable { + /// + /// The path to the updated assembly to run if any. Populated once returns + /// + string UpdatePath { get; } + /// /// Runs the /// diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 02cc380729..902f3f3802 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -5,9 +5,12 @@ namespace Tgstation.Server.Host { /// sealed class Server : IServer - { - /// - public void Dispose() { } + { + /// + public string UpdatePath => null; + + /// + public void Dispose() { } /// public Task RunAsync(string[] args, CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs index 103b66c410..e016e73849 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs +++ b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Console.Tests { @@ -11,11 +12,11 @@ namespace Tgstation.Server.Host.Console.Tests [TestMethod] public async Task TestProgramRuns() { - var mockServer = new Mock(); + var mockServer = new Mock(); mockServer.Setup(x => x.RunAsync(null, It.IsAny())).Returns(Task.CompletedTask).Verifiable(); - var mockServerFactory = new Mock(); - mockServerFactory.Setup(x => x.CreateServer()).Returns(mockServer.Object).Verifiable(); - Program.ServerFactory = mockServerFactory.Object; + var mockServerFactory = new Mock(); + mockServerFactory.Setup(x => x.CreateWatchdog()).Returns(mockServer.Object).Verifiable(); + Program.WatchdogFactory = mockServerFactory.Object; await Program.Main(null).ConfigureAwait(false); mockServer.VerifyAll(); mockServerFactory.VerifyAll(); From 1ae33c059a34f823af164c3c9e0c99bbaf7b71c4 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 6 Apr 2018 01:46:14 -0400 Subject: [PATCH 052/397] Fix release and docker build. Remove 1709 until we're ready to use it --- build/Dockerfile | 5 +++ build/Dockerfile1709 | 37 ------------------- src/Tgstation.Server.Host.Console/Program.cs | 2 +- .../ServerService.cs | 4 +- .../IWatchdog.cs | 3 +- tgstation-server.sln | 5 +-- 6 files changed, 11 insertions(+), 45 deletions(-) delete mode 100644 build/Dockerfile1709 diff --git a/build/Dockerfile b/build/Dockerfile index 1c99478ef7..5da6912dec 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -4,11 +4,13 @@ WORKDIR /src COPY tgstation-server.sln ./ COPY src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj src/Tgstation.Server.Host.Console/ +COPY src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj src/Tgstation.Server.Host.Watchdog/ COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/ COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/ COPY tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj tests/Tgstation.Server.Api.Tests/ COPY tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj tests/Tgstation.Server.Host.Tests/ +COPY tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj tests/Tgstation.Server.Host.Watchdog.Tests/ COPY tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj tests/Tgstation.Server.Host.Console.Tests/ RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=false @@ -24,6 +26,9 @@ RUN dotnet test -c Release WORKDIR /src/tests/Tgstation.Server.Host.Tests RUN dotnet test -c Release +WORKDIR /src/tests/Tgstation.Server.Host.Watchdog.Tests +RUN dotnet test -c Release + WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests RUN dotnet test -c Release diff --git a/build/Dockerfile1709 b/build/Dockerfile1709 deleted file mode 100644 index 2d3b7ddc8d..0000000000 --- a/build/Dockerfile1709 +++ /dev/null @@ -1,37 +0,0 @@ -FROM microsoft/dotnet:2.1-sdk-nanoserver-1709 AS build -WORKDIR /src - -COPY tgstation-server.sln ./ - -COPY src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj src/Tgstation.Server.Host.Console/ -COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/ -COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/ - -COPY tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj tests/Tgstation.Server.Api.Tests/ -COPY tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj tests/Tgstation.Server.Host.Tests/ -COPY tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj tests/Tgstation.Server.Host.Console.Tests/ - -RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=false - -COPY . . - -WORKDIR /src/src/Tgstation.Server.Host.Console -RUN dotnet build -c Release -o /app - -WORKDIR /src/tests/Tgstation.Server.Api.Tests -RUN dotnet test -c Release - -WORKDIR /src/tests/Tgstation.Server.Host.Tests -RUN dotnet test -c Release - -WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests -RUN dotnet test -c Release - -FROM build AS publish -RUN dotnet publish -c Release -o /app - -FROM microsoft/dotnet:2.1-runtime-nanoserver-1709 -EXPOSE 80 -WORKDIR /app -COPY --from=publish /app . -ENTRYPOINT ["dotnet", "Tgstation.Server.Host.Console.dll"] diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index 8beea9b3f4..f7ab19864e 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Console static class Program { /// - /// The for the + /// The for the /// internal static IWatchdogFactory WatchdogFactory { get; set; } = new WatchdogFactory(); diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs index 5cd218e587..761e46c66b 100644 --- a/src/Tgstation.Server.Host.Service/ServerService.cs +++ b/src/Tgstation.Server.Host.Service/ServerService.cs @@ -6,7 +6,7 @@ using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Service { /// - /// Represents a as a + /// Represents a as a /// sealed class ServerService : ServiceBase { @@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Service IWatchdog watchdog; /// - /// The recieved from of + /// The recieved from of /// Task watchdogTask; diff --git a/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs index 3736377287..834ab78820 100644 --- a/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/IWatchdog.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Watchdog diff --git a/tgstation-server.sln b/tgstation-server.sln index 071f4c77f5..8e80e05dbf 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -20,7 +20,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6 ProjectSection(SolutionItems) = preProject build\BuildDox.ps1 = build\BuildDox.ps1 build\Dockerfile = build\Dockerfile - build\Dockerfile1709 = build\Dockerfile1709 build\Doxyfile = build\Doxyfile build\tgs.ico = build\tgs.ico build\UploadCoverage.ps1 = build\UploadCoverage.ps1 @@ -50,9 +49,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Tests EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Watchdog", "src\Tgstation.Server.Host.Watchdog\Tgstation.Server.Host.Watchdog.csproj", "{5D2D682C-6BF0-439C-850B-6AB945BBEAEA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watchdog", "src\Tgstation.Server.Host.Watchdog\Tgstation.Server.Host.Watchdog.csproj", "{5D2D682C-6BF0-439C-850B-6AB945BBEAEA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Watchdog.Tests", "tests\Tgstation.Server.Host.Watchdog.Tests\Tgstation.Server.Host.Watchdog.Tests.csproj", "{7500F776-4384-4B5F-A8D8-22461CAD108B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watchdog.Tests", "tests\Tgstation.Server.Host.Watchdog.Tests\Tgstation.Server.Host.Watchdog.Tests.csproj", "{7500F776-4384-4B5F-A8D8-22461CAD108B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From e87f664db8775da5f7259e41b2e133f854e6f665 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 12:38:27 -0400 Subject: [PATCH 053/397] Get the watchdog working --- build/Dockerfile | 5 +- .../IServer.cs | 2 +- .../IServerFactory.cs | 2 +- .../Tgstation.Server.Host.Startup.csproj | 17 ++++ .../IActiveAssemblyDeleter.cs | 16 ++++ .../IsolatedServerFactory.cs | 45 +++++++++++ .../NativeMethods.cs | 21 +++++ .../PosixActiveAssemblyDeleter.cs | 15 ++++ .../Tgstation.Server.Host.Watchdog.csproj | 6 ++ .../Watchdog.cs | 79 ++++++++++--------- .../WatchdogFactory.cs | 6 +- .../WindowsActiveAssemblyDeleter.cs | 32 ++++++++ src/Tgstation.Server.Host/Server.cs | 3 +- src/Tgstation.Server.Host/ServerFactory.cs | 4 +- .../Tgstation.Server.Host.csproj | 1 + tgstation-server.sln | 6 ++ 16 files changed, 213 insertions(+), 47 deletions(-) rename src/{Tgstation.Server.Host => Tgstation.Server.Host.Startup}/IServer.cs (95%) rename src/{Tgstation.Server.Host => Tgstation.Server.Host.Startup}/IServerFactory.cs (85%) create mode 100644 src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj create mode 100644 src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/NativeMethods.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs diff --git a/build/Dockerfile b/build/Dockerfile index 5da6912dec..5332fa7e91 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -5,6 +5,7 @@ COPY tgstation-server.sln ./ COPY src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj src/Tgstation.Server.Host.Console/ COPY src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj src/Tgstation.Server.Host.Watchdog/ +COPY src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj src/Tgstation.Server.Host.Startup/ COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/ COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/ @@ -17,8 +18,8 @@ RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=fa COPY . . -WORKDIR /src/src/Tgstation.Server.Host.Console -RUN dotnet build -c Release -o /app +WORKDIR /src/src/Tgstation.Server.Host.Startup +RUN dotnet build -c Release WORKDIR /src/tests/Tgstation.Server.Api.Tests RUN dotnet test -c Release diff --git a/src/Tgstation.Server.Host/IServer.cs b/src/Tgstation.Server.Host.Startup/IServer.cs similarity index 95% rename from src/Tgstation.Server.Host/IServer.cs rename to src/Tgstation.Server.Host.Startup/IServer.cs index 4db6422790..8dad58bdcc 100644 --- a/src/Tgstation.Server.Host/IServer.cs +++ b/src/Tgstation.Server.Host.Startup/IServer.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host +namespace Tgstation.Server.Host.Startup { /// /// Represents the host diff --git a/src/Tgstation.Server.Host/IServerFactory.cs b/src/Tgstation.Server.Host.Startup/IServerFactory.cs similarity index 85% rename from src/Tgstation.Server.Host/IServerFactory.cs rename to src/Tgstation.Server.Host.Startup/IServerFactory.cs index 7a5efbc7b2..43668a1049 100644 --- a/src/Tgstation.Server.Host/IServerFactory.cs +++ b/src/Tgstation.Server.Host.Startup/IServerFactory.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host +namespace Tgstation.Server.Host.Startup { /// /// For creating s diff --git a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj new file mode 100644 index 0000000000..5f12e43ecd --- /dev/null +++ b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + + + + latest + true + + bin\Release\netstandard2.0\Tgstation.Server.Host.Startup..xml + + + + latest + + diff --git a/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs new file mode 100644 index 0000000000..aa6d361490 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs @@ -0,0 +1,16 @@ +using System.Reflection; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// For deleting s used by the program + /// + interface IActiveAssemblyDeleter + { + /// + /// Deletes an that is in use by the runtime + /// + /// The to delete + void DeleteActiveAssembly(Assembly assembly); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs new file mode 100644 index 0000000000..c7fe7f49cc --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using Tgstation.Server.Host.Startup; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// for loading s in a different + /// + sealed class IsolatedServerFactory : AssemblyLoadContext, IServerFactory + { + /// + /// The path of the to load + /// + readonly string assemblyPath; + + /// + /// Construct a + /// + /// The value of + public IsolatedServerFactory(string assemblyPath) => this.assemblyPath = assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath)); + + /// + /// Loads the at and creates an from it + /// + /// A new + public IServer CreateServer() + { + var assembly = LoadFromAssemblyPath(assemblyPath); + //find the IServerFactory implementation + + var serverFactoryInterfaceType = typeof(IServerFactory); + var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); + + var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); + return serverFactory.CreateServer(); + } + + //honestly have no idea what this is for, but the examples i see just return null and it seems to work just fine + /// + protected override Assembly Load(AssemblyName assemblyName) => null; + } +} diff --git a/src/Tgstation.Server.Host.Watchdog/NativeMethods.cs b/src/Tgstation.Server.Host.Watchdog/NativeMethods.cs new file mode 100644 index 0000000000..1ce03329f7 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/NativeMethods.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Watchdog +{ + static class NativeMethods + { + public enum MoveFileFlags + { + None = 0, + ReplaceExisting = 1, + CopyAllowed = 2, + DelayUntilReboot = 4, + WriteThrough = 8, + CreateHardlink = 16, + FailIfNotTrackable = 32, + } + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs new file mode 100644 index 0000000000..d4f0fdbf17 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs @@ -0,0 +1,15 @@ +using System; +using System.IO; +using System.Reflection; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// See for POSIX systems + /// + sealed class PosixActiveAssemblyDeleter : IActiveAssemblyDeleter + { + /// + public void DeleteActiveAssembly(Assembly assembly) => File.Delete(assembly?.Location ?? throw new ArgumentNullException(nameof(assembly))); //glory of inodes + } +} diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index d90f320a9d..d6e2d0f05e 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -18,6 +18,12 @@ + + + + + + diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index f655debe8b..1080bc6bc0 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -1,58 +1,59 @@ using System; using System.IO; -using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host.Watchdog { /// - sealed class Watchdog : MarshalByRefObject, IWatchdog + sealed class Watchdog : IWatchdog { - string DomainName => GetType().Namespace.Replace(nameof(Watchdog), String.Empty); + /// + /// The initial for the + /// + readonly IServerFactory initialServerFactory; - string DllName => String.Concat(DomainName, ".dll"); + /// + /// The for the + /// + readonly IActiveAssemblyDeleter activeAssemblyDeleter; - async Task RunServer(string[] args, CancellationToken cancellationToken) - { - var assembly = Assembly.LoadFrom(DllName); - //the only thing we need to reflect is the IServerFactory, we can cross reference everything else from there - var factoryInterfaceType = assembly.GetType(String.Concat(DomainName, ".IServerFactory")); - var factoryType = assembly.GetTypes().Where(x => factoryInterfaceType.IsAssignableFrom(x)).First(); - var factory = Activator.CreateInstance(factoryType); - var factoryFunction = factoryInterfaceType.GetMethods().First(); - var serverType = factoryFunction.ReturnType; - var serverRunFunction = serverType.GetMethods().First(); - var serverUpdatePathAccessor = serverType.GetProperties().First().GetAccessors().First(); - - var server = (IDisposable)factoryFunction.Invoke(factory, Array.Empty()); - using (server) - { - var task = (Task)serverRunFunction.Invoke(server, new object[] { args, cancellationToken }); - await task.ConfigureAwait(false); - return (string)serverUpdatePathAccessor.Invoke(server, Array.Empty()); - } - } + /// + /// Construct a + /// + /// The value of + /// The value of + public Watchdog(IServerFactory initialServerFactory, IActiveAssemblyDeleter activeAssemblyDeleter) + { + this.initialServerFactory = initialServerFactory ?? throw new ArgumentNullException(nameof(initialServerFactory)); + this.activeAssemblyDeleter = activeAssemblyDeleter ?? throw new ArgumentNullException(nameof(activeAssemblyDeleter)); + } /// public async Task RunAsync(string[] args, CancellationToken cancellationToken) { - while (!cancellationToken.IsCancellationRequested) - { - string updatePath = null; + //first run the host we started with + var serverFactory = initialServerFactory; + var assembly = serverFactory.GetType().Assembly; + do + { + string updatePath; + using (var server = serverFactory.CreateServer()) + { + serverFactory = null; + await server.RunAsync(args, cancellationToken).ConfigureAwait(false); + updatePath = server.UpdatePath; + } - await Task.CompletedTask.ConfigureAwait(false); - - if (updatePath == null) - break; - - //Ensure the assembly is unloaded - GC.Collect(Int32.MaxValue, GCCollectionMode.Default, true); - - File.Delete(DllName); - File.Move(updatePath, DllName); - } + if (updatePath == null) + break; + + activeAssemblyDeleter.DeleteActiveAssembly(assembly); + File.Move(updatePath, assembly.Location); + serverFactory = new IsolatedServerFactory(assembly.Location); + } + while (!cancellationToken.IsCancellationRequested); } } } diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs index d1dab42db5..c460d89448 100644 --- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -1,9 +1,11 @@ -namespace Tgstation.Server.Host.Watchdog +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Watchdog { /// public sealed class WatchdogFactory : IWatchdogFactory { /// - public IWatchdog CreateWatchdog() => new Watchdog(); + public IWatchdog CreateWatchdog() => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter()); } } diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs new file mode 100644 index 0000000000..85b9315095 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// See for Windows systems + /// + sealed class WindowsActiveAssemblyDeleter : IActiveAssemblyDeleter + { + /// + public void DeleteActiveAssembly(Assembly assembly) + { + if (assembly == null) + throw new ArgumentNullException(nameof(assembly)); + + var location = assembly.Location; + //Can't use Path.GetTempFileName() because it may cross drives, which won't actually rename the file + //Also append the long path prefix just in case we're running on .NET framework + var tmpLocation = String.Concat(@"\\?\", assembly.Location, Guid.NewGuid()); + File.Delete(tmpLocation); + File.Move(location, tmpLocation); + + //this deletes the file at reboot, sadly it also triggers a restart notification on server os's but oh well + if (!NativeMethods.MoveFileEx(tmpLocation, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } +} diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 902f3f3802..d0df99aa60 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -1,5 +1,6 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host { @@ -7,7 +8,7 @@ namespace Tgstation.Server.Host sealed class Server : IServer { /// - public string UpdatePath => null; + public string UpdatePath => "Tgstation.Server.Host.New.dll"; /// public void Dispose() { } diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index d22e9938ee..e0f9291516 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Host +using Tgstation.Server.Host.Startup; + +namespace Tgstation.Server.Host { /// public sealed class ServerFactory : IServerFactory diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 6082d6d99c..d624c0ae7b 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -22,6 +22,7 @@ + diff --git a/tgstation-server.sln b/tgstation-server.sln index 8e80e05dbf..0aeda4e3e6 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watch EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watchdog.Tests", "tests\Tgstation.Server.Host.Watchdog.Tests\Tgstation.Server.Host.Watchdog.Tests.csproj", "{7500F776-4384-4B5F-A8D8-22461CAD108B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Startup", "src\Tgstation.Server.Host.Startup\Tgstation.Server.Host.Startup.csproj", "{7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,6 +125,10 @@ Global {7500F776-4384-4B5F-A8D8-22461CAD108B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.Build.0 = Release|Any CPU + {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 5c7ee1f2d65416c99d9f76a390549b821f4f904b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 14:06:31 -0400 Subject: [PATCH 054/397] Watchdog tests --- .../Tgstation.Server.Api.csproj | 1 + .../Tgstation.Server.Client.csproj | 1 + .../Tgstation.Server.CommandLine.csproj | 1 + .../Tgstation.Server.Host.Console.csproj | 1 - .../GlobalSuppressions.cs | Bin 1660 -> 1230 bytes .../Tgstation.Server.Host.Startup.csproj | 1 + .../AssemblyInfo.cs | 4 + .../IActiveAssemblyDeleter.cs | 4 +- .../IIsolatedAssemblyContextFactory.cs | 17 ++++ .../IOSIdentifier.cs | 13 +++ .../IsolatedAssemblyContextFactory.cs | 11 +++ .../OSIdentifier.cs | 11 +++ .../PosixActiveAssemblyDeleter.cs | 2 +- .../Tgstation.Server.Host.Watchdog.csproj | 1 + .../Watchdog.cs | 17 ++-- .../WatchdogFactory.cs | 8 +- .../WindowsActiveAssemblyDeleter.cs | 32 ++++--- src/Tgstation.Server.Host/Server.cs | 2 +- .../Tgstation.Server.Host.csproj | 1 + .../Tgstation.Server.Host.Tests/TestServer.cs | 3 + .../TestIsolatedAssemblyContextFactory.cs | 18 ++++ .../TestIsolatedServerFactory.cs | 26 ++++++ .../TestOSIdentifier.cs | 19 +++++ .../TestPosixActiveAssemblyDeleter.cs | 40 +++++++++ .../TestWatchdog.cs | 78 ++++++++++++++++++ .../TestWatchdogFactory.cs | 34 ++++++++ .../TestWindowsActiveAssemblyDeleter.cs | 48 +++++++++++ ...gstation.Server.Host.Watchdog.Tests.csproj | 5 +- 28 files changed, 370 insertions(+), 29 deletions(-) create mode 100644 src/Tgstation.Server.Host.Watchdog/AssemblyInfo.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/IIsolatedAssemblyContextFactory.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/IOSIdentifier.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/OSIdentifier.cs create mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedAssemblyContextFactory.cs create mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs create mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestOSIdentifier.cs create mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestPosixActiveAssemblyDeleter.cs create mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs create mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs create mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestWindowsActiveAssemblyDeleter.cs diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 55365cd82f..942c0a5227 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -2,6 +2,7 @@ netstandard2.0 + Full diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index c5553e1517..ca8a985c9a 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -2,6 +2,7 @@ netstandard2.0 + Full 4.0.0.0 false Cyberboss diff --git a/src/Tgstation.Server.CommandLine/Tgstation.Server.CommandLine.csproj b/src/Tgstation.Server.CommandLine/Tgstation.Server.CommandLine.csproj index 1e5abf7f56..1da32f73c0 100644 --- a/src/Tgstation.Server.CommandLine/Tgstation.Server.CommandLine.csproj +++ b/src/Tgstation.Server.CommandLine/Tgstation.Server.CommandLine.csproj @@ -3,6 +3,7 @@ Exe netcoreapp2.0 + Full diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index 536fe76b0a..5bd446923d 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -4,7 +4,6 @@ Exe netcoreapp2.0 Full - diff --git a/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs b/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs index e314a34cffba3b0b8ba16d57dbb1417779a37046..2dac9c7cde3850e805688d14e0a28ef43032c0e9 100644 GIT binary patch delta 11 ScmeyvbB=RD-)0*o2NnPy8U!-{ delta 303 zcmaKn!3qIU6o$WKL%f4zqa;cxWq}O~HZxm`iHoT*GtHo^yn(!m_mC&D@XsBx)9IdO z{`24OKlfRBRc_bI0*^gDAzSLSsHsmF(WOs^9s_2qF`6Ib+UOV(QXFo`J07XEdHcI6Bd>uV)Y m?sWB+Sn%NwwXIdNC!B0=w3!N@1^)*A2ijywl`{9jqxc4L?lyA( diff --git a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj index 5f12e43ecd..e9797da445 100644 --- a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj +++ b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj @@ -2,6 +2,7 @@ netstandard2.0 + Full diff --git a/src/Tgstation.Server.Host.Watchdog/AssemblyInfo.cs b/src/Tgstation.Server.Host.Watchdog/AssemblyInfo.cs new file mode 100644 index 0000000000..c2d4b6d5d9 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Tgstation.Server.Host.Watchdog.Tests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs index aa6d361490..dab5fb890b 100644 --- a/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs +++ b/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Watchdog /// /// Deletes an that is in use by the runtime /// - /// The to delete - void DeleteActiveAssembly(Assembly assembly); + /// The of the to delete + void DeleteActiveAssembly(string assemblyPath); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IIsolatedAssemblyContextFactory.cs b/src/Tgstation.Server.Host.Watchdog/IIsolatedAssemblyContextFactory.cs new file mode 100644 index 0000000000..88f90cf4ea --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/IIsolatedAssemblyContextFactory.cs @@ -0,0 +1,17 @@ +using Tgstation.Server.Host.Startup; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// For creating s in an isolated context from an + /// + interface IIsolatedAssemblyContextFactory + { + /// + /// Create a for an unloaded + /// + /// The path to the to create the from + /// A new + IServerFactory CreateIsolatedServerFactory(string assemblyPath); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IOSIdentifier.cs b/src/Tgstation.Server.Host.Watchdog/IOSIdentifier.cs new file mode 100644 index 0000000000..46c35da7c8 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/IOSIdentifier.cs @@ -0,0 +1,13 @@ +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// For gathering runtime OS information + /// + interface IOSIdentifier + { + /// + /// If the operating system is Win32 based + /// + bool IsWindows { get; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs new file mode 100644 index 0000000000..6f97144265 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs @@ -0,0 +1,11 @@ +using Tgstation.Server.Host.Startup; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + sealed class IsolatedAssemblyContextFactory : IIsolatedAssemblyContextFactory + { + /// + public IServerFactory CreateIsolatedServerFactory(string assemblyPath) => new IsolatedServerFactory(assemblyPath); + } +} diff --git a/src/Tgstation.Server.Host.Watchdog/OSIdentifier.cs b/src/Tgstation.Server.Host.Watchdog/OSIdentifier.cs new file mode 100644 index 0000000000..e5e483e3d2 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/OSIdentifier.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + sealed class OSIdentifier : IOSIdentifier + { + /// + public bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs index d4f0fdbf17..e626ecddf4 100644 --- a/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs +++ b/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs @@ -10,6 +10,6 @@ namespace Tgstation.Server.Host.Watchdog sealed class PosixActiveAssemblyDeleter : IActiveAssemblyDeleter { /// - public void DeleteActiveAssembly(Assembly assembly) => File.Delete(assembly?.Location ?? throw new ArgumentNullException(nameof(assembly))); //glory of inodes + public void DeleteActiveAssembly(string assemblyPath) => File.Delete(assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath))); //glory of inodes } } diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index d6e2d0f05e..157f22838f 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -2,6 +2,7 @@ netstandard2.0 + Full diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 1080bc6bc0..540955d094 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -19,15 +19,22 @@ namespace Tgstation.Server.Host.Watchdog /// readonly IActiveAssemblyDeleter activeAssemblyDeleter; + /// + /// The for the + /// + readonly IIsolatedAssemblyContextFactory isolatedAssemblyLoader; + /// /// Construct a /// /// The value of /// The value of - public Watchdog(IServerFactory initialServerFactory, IActiveAssemblyDeleter activeAssemblyDeleter) + /// The value of + public Watchdog(IServerFactory initialServerFactory, IActiveAssemblyDeleter activeAssemblyDeleter, IIsolatedAssemblyContextFactory isolatedAssemblyLoader) { this.initialServerFactory = initialServerFactory ?? throw new ArgumentNullException(nameof(initialServerFactory)); this.activeAssemblyDeleter = activeAssemblyDeleter ?? throw new ArgumentNullException(nameof(activeAssemblyDeleter)); + this.isolatedAssemblyLoader = isolatedAssemblyLoader ?? throw new ArgumentNullException(nameof(isolatedAssemblyLoader)); } /// @@ -35,7 +42,7 @@ namespace Tgstation.Server.Host.Watchdog { //first run the host we started with var serverFactory = initialServerFactory; - var assembly = serverFactory.GetType().Assembly; + var assemblyPath = serverFactory.GetType().Assembly.Location; do { string updatePath; @@ -49,9 +56,9 @@ namespace Tgstation.Server.Host.Watchdog if (updatePath == null) break; - activeAssemblyDeleter.DeleteActiveAssembly(assembly); - File.Move(updatePath, assembly.Location); - serverFactory = new IsolatedServerFactory(assembly.Location); + activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath); + File.Move(updatePath, assemblyPath); + serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath); } while (!cancellationToken.IsCancellationRequested); } diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs index c460d89448..6b5c9dabe3 100644 --- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -1,11 +1,11 @@ -using System.Runtime.InteropServices; - -namespace Tgstation.Server.Host.Watchdog +namespace Tgstation.Server.Host.Watchdog { /// public sealed class WatchdogFactory : IWatchdogFactory { + internal static IOSIdentifier osIdentifier { get; set; } = new OSIdentifier(); + /// - public IWatchdog CreateWatchdog() => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter()); + public IWatchdog CreateWatchdog() => new Watchdog(new ServerFactory(), osIdentifier.IsWindows ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory()); } } diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs index 85b9315095..811c5bee3a 100644 --- a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs +++ b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs @@ -1,7 +1,7 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Reflection; using System.Runtime.InteropServices; namespace Tgstation.Server.Host.Watchdog @@ -11,22 +11,28 @@ namespace Tgstation.Server.Host.Watchdog /// sealed class WindowsActiveAssemblyDeleter : IActiveAssemblyDeleter { - /// - public void DeleteActiveAssembly(Assembly assembly) + /// + /// Set a file located at to be deleted on reboot + /// + /// The file to delete on reboot + [ExcludeFromCodeCoverage] + static void DeleteFileOnReboot(string path) { - if (assembly == null) - throw new ArgumentNullException(nameof(assembly)); + if (!NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + /// + public void DeleteActiveAssembly(string assemblyPath) + { + if (assemblyPath == null) + throw new ArgumentNullException(nameof(assemblyPath)); - var location = assembly.Location; //Can't use Path.GetTempFileName() because it may cross drives, which won't actually rename the file //Also append the long path prefix just in case we're running on .NET framework - var tmpLocation = String.Concat(@"\\?\", assembly.Location, Guid.NewGuid()); - File.Delete(tmpLocation); - File.Move(location, tmpLocation); - - //this deletes the file at reboot, sadly it also triggers a restart notification on server os's but oh well - if (!NativeMethods.MoveFileEx(tmpLocation, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) - throw new Win32Exception(Marshal.GetLastWin32Error()); + var tmpLocation = String.Concat(@"\\?\", assemblyPath, Guid.NewGuid()); + File.Move(assemblyPath, tmpLocation); + DeleteFileOnReboot(tmpLocation); } } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index d0df99aa60..9128c38c24 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host sealed class Server : IServer { /// - public string UpdatePath => "Tgstation.Server.Host.New.dll"; + public string UpdatePath => null; /// public void Dispose() { } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index d624c0ae7b..d204eda12b 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -2,6 +2,7 @@ netstandard2.0 + Full diff --git a/tests/Tgstation.Server.Host.Tests/TestServer.cs b/tests/Tgstation.Server.Host.Tests/TestServer.cs index 3263577584..5e20bd627e 100644 --- a/tests/Tgstation.Server.Host.Tests/TestServer.cs +++ b/tests/Tgstation.Server.Host.Tests/TestServer.cs @@ -10,7 +10,10 @@ namespace Tgstation.Server.Host.Tests public async Task TestRunAndDispose() { using (var server = new Server()) + { await server.RunAsync(null, default).ConfigureAwait(false); + Assert.IsNull(server.UpdatePath); + } } } } diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedAssemblyContextFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedAssemblyContextFactory.cs new file mode 100644 index 0000000000..2f2e2d0f7d --- /dev/null +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedAssemblyContextFactory.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Tgstation.Server.Host.Watchdog.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestIsolatedAssemblyContextFactory + { + [TestMethod] + public void TestServerFactoryCreation() + { + var contextFactory = new IsolatedAssemblyContextFactory(); + Assert.IsNotNull(contextFactory.CreateIsolatedServerFactory(typeof(ServerFactory).Assembly.Location)); + } + } +} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs new file mode 100644 index 0000000000..33187c485b --- /dev/null +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Tgstation.Server.Host.Watchdog.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestIsolatedServerFactory + { + [TestMethod] + public void TestConstruction() + { + Assert.ThrowsException(() => new IsolatedServerFactory(null)); + var isf = new IsolatedServerFactory(typeof(ServerFactory).Assembly.Location); + } + + [TestMethod] + public void TestLoading() + { + var isf = new IsolatedServerFactory(typeof(ServerFactory).Assembly.Location); + Assert.IsNotNull(isf.CreateServer()); + } + } +} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestOSIdentifier.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestOSIdentifier.cs new file mode 100644 index 0000000000..0c0cf1b9e2 --- /dev/null +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestOSIdentifier.cs @@ -0,0 +1,19 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Watchdog.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestOSIdentifier + { + [TestMethod] + public void TestOSIdentification() + { + var identifier = new OSIdentifier(); + Assert.AreEqual(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), identifier.IsWindows); + } + } +} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestPosixActiveAssemblyDeleter.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestPosixActiveAssemblyDeleter.cs new file mode 100644 index 0000000000..9f77307950 --- /dev/null +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestPosixActiveAssemblyDeleter.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; + +namespace Tgstation.Server.Host.Watchdog.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestPosixActiveAssemblyDeleter + { + [TestMethod] + public void TestAssemblyDeletion() + { + var ourAssembly = GetType().Assembly; + var fakeAssemblyPath = String.Concat(ourAssembly.Location, Guid.NewGuid()); + File.Copy(ourAssembly.Location, fakeAssemblyPath); + + try + { + var deleter = new PosixActiveAssemblyDeleter(); + deleter.DeleteActiveAssembly(fakeAssemblyPath); + Assert.IsFalse(File.Exists(fakeAssemblyPath)); + } + catch + { + File.Delete(fakeAssemblyPath); + throw; + } + } + + [TestMethod] + public void TestNullInvoke() + { + var deleter = new PosixActiveAssemblyDeleter(); + Assert.ThrowsException(() => deleter.DeleteActiveAssembly(null)); + } + } +} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs new file mode 100644 index 0000000000..00b492d110 --- /dev/null +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs @@ -0,0 +1,78 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Startup; + +namespace Tgstation.Server.Host.Watchdog.Tests +{ + [TestClass] + public sealed class TestWatchdog + { + [TestMethod] + public void TestConstruction() + { + Assert.ThrowsException(() => new Watchdog(null, null, null)); + var mockServerFactory = new Mock(); + Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, null, null)); + var mockActiveAssemblyDeleter = new Mock(); + Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, null)); + var mockIsolatedServerContextFactory = new Mock(); + var wd = new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object); + } + + class MockServerFactory : IServerFactory + { + readonly IServer server; + public MockServerFactory(IServer server) => this.server = server; + public IServer CreateServer() => server; + } + + [TestMethod] + public async Task TestRunAsyncWithoutUpdate() + { + var mockServer = new Mock(); + var mockServerFactory = new MockServerFactory(mockServer.Object); + var mockActiveAssemblyDeleter = new Mock(); + var mockIsolatedServerContextFactory = new Mock(); + + var wd = new Watchdog(mockServerFactory, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object); + + using (var cts = new CancellationTokenSource()) + { + mockServer.Setup(x => x.RunAsync(It.IsNotNull(), cts.Token)).Returns(Task.CompletedTask).Verifiable(); + await wd.RunAsync(Array.Empty(), cts.Token).ConfigureAwait(false); + mockServer.VerifyAll(); + } + } + + [TestMethod] + public async Task TestRunAsyncWithUpdate() + { + var mockServer = new Mock(); + mockServer.Setup(x => x.UpdatePath).Returns(GetType().Assembly.Location).Verifiable(); + var mockServerFactory = new MockServerFactory(mockServer.Object); + var mockActiveAssemblyDeleter = new Mock(); + var mockIsolatedServerContextFactory = new Mock(); + mockIsolatedServerContextFactory.Setup(x => x.CreateIsolatedServerFactory(GetType().Assembly.Location)).Returns(mockServerFactory).Verifiable(); + + var wd = new Watchdog(mockServerFactory, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object); + + using (var cts = new CancellationTokenSource()) + { + int count = 0; + mockServer.Setup(x => x.RunAsync(It.IsNotNull(), cts.Token)).Callback(() => + { + if (++count > 1) + cts.Cancel(); + }).Returns(Task.CompletedTask).Verifiable(); + await wd.RunAsync(Array.Empty(), cts.Token).ConfigureAwait(false); + + mockServer.VerifyAll(); + mockActiveAssemblyDeleter.Verify(x => x.DeleteActiveAssembly(GetType().Assembly.Location), Times.Exactly(2)); + mockIsolatedServerContextFactory.VerifyAll(); + } + } + } +} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs new file mode 100644 index 0000000000..f5528160b9 --- /dev/null +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs @@ -0,0 +1,34 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Tgstation.Server.Host.Watchdog.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestWatchdogFactory + { + [TestMethod] + public void TestCreateWatchdogWindows() + { + var factory = new WatchdogFactory(); + var mockOSIdentifier = new Mock(); + mockOSIdentifier.SetupGet(x => x.IsWindows).Returns(true).Verifiable(); + WatchdogFactory.osIdentifier = mockOSIdentifier.Object; + Assert.IsNotNull(factory.CreateWatchdog()); + mockOSIdentifier.VerifyAll(); + } + + [TestMethod] + public void TestCreateWatchdogNonWindows() + { + var factory = new WatchdogFactory(); + var mockOSIdentifier = new Mock(); + mockOSIdentifier.SetupGet(x => x.IsWindows).Returns(false).Verifiable(); + WatchdogFactory.osIdentifier = mockOSIdentifier.Object; + Assert.IsNotNull(factory.CreateWatchdog()); + mockOSIdentifier.VerifyAll(); + } + } +} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWindowsActiveAssemblyDeleter.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWindowsActiveAssemblyDeleter.cs new file mode 100644 index 0000000000..70e0a326c9 --- /dev/null +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWindowsActiveAssemblyDeleter.cs @@ -0,0 +1,48 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.ComponentModel; +using System.IO; + +namespace Tgstation.Server.Host.Watchdog.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestWindowsActiveAssemblyDeleter + { + [TestMethod] + public void TestAssemblyDeletion() + { + var ourAssembly = GetType().Assembly; + var fakeAssemblyPath = String.Concat(ourAssembly.Location, Guid.NewGuid()); + File.Copy(ourAssembly.Location, fakeAssemblyPath); + + try + { + var deleter = new WindowsActiveAssemblyDeleter(); + try + { + deleter.DeleteActiveAssembly(fakeAssemblyPath); + } + catch (Win32Exception e) + { + Assert.AreEqual(e.NativeErrorCode, 5); + } + Assert.IsFalse(File.Exists(fakeAssemblyPath)); + } + catch + { + File.Delete(fakeAssemblyPath); + throw; + } + } + + [TestMethod] + public void TestNullInvoke() + { + var deleter = new WindowsActiveAssemblyDeleter(); + Assert.ThrowsException(() => deleter.DeleteActiveAssembly(null)); + } + } +} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj index e2e2dcaebc..4ac1b59a9d 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj @@ -19,8 +19,9 @@ - - + + + From 3fdeee612eb1f4b9a192e631460c7fc0932511e2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 14:13:45 -0400 Subject: [PATCH 055/397] Fix Server.cs indentation --- src/Tgstation.Server.Host/Server.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 9128c38c24..50406a988d 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -6,12 +6,12 @@ namespace Tgstation.Server.Host { /// sealed class Server : IServer - { - /// - public string UpdatePath => null; + { + /// + public string UpdatePath => null; - /// - public void Dispose() { } + /// + public void Dispose() { } /// public Task RunAsync(string[] args, CancellationToken cancellationToken) => Task.CompletedTask; From 06b8199d0d149094c6e8142d8a1503b592a722b4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 16:14:15 -0400 Subject: [PATCH 056/397] Fix release build --- .../IActiveAssemblyDeleter.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs index dab5fb890b..70c5762154 100644 --- a/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs +++ b/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs @@ -1,16 +1,14 @@ -using System.Reflection; - -namespace Tgstation.Server.Host.Watchdog +namespace Tgstation.Server.Host.Watchdog { /// - /// For deleting s used by the program + /// For deleting s used by the program /// interface IActiveAssemblyDeleter { /// - /// Deletes an that is in use by the runtime + /// Deletes an that is in use by the runtime /// - /// The of the to delete + /// The of the to delete void DeleteActiveAssembly(string assemblyPath); } } \ No newline at end of file From 13e1676980598639d568f6dc394bb62e41605de1 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 16:54:41 -0400 Subject: [PATCH 057/397] Add service tests --- src/Tgstation.Server.Host.Service/Program.cs | 4 +- .../ServerService.cs | 9 +- .../Properties/AssemblyInfo.cs | 11 +++ .../TestServerService.cs | 53 +++++++++++ ...Tgstation.Server.Host.Service.Tests.csproj | 92 +++++++++++++++++++ .../packages.config | 9 ++ ...gstation.Server.Host.Watchdog.Tests.csproj | 4 +- tgstation-server.sln | 6 ++ 8 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs create mode 100644 tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs create mode 100644 tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj create mode 100644 tests/Tgstation.Server.Host.Service.Tests/packages.config diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 53773df933..2bbb97239c 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -1,4 +1,5 @@ -using System.ServiceProcess; +using System.Diagnostics.CodeAnalysis; +using System.ServiceProcess; using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Service @@ -11,6 +12,7 @@ namespace Tgstation.Server.Host.Service /// /// Entrypoint for the application /// + [ExcludeFromCodeCoverage] static void Main() => ServiceBase.Run(new ServerService(new WatchdogFactory())); } } diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs index 761e46c66b..c7aad011cf 100644 --- a/src/Tgstation.Server.Host.Service/ServerService.cs +++ b/src/Tgstation.Server.Host.Service/ServerService.cs @@ -1,4 +1,6 @@ -using System.ServiceProcess; +using System; +using System.Diagnostics.CodeAnalysis; +using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Watchdog; @@ -31,14 +33,17 @@ namespace Tgstation.Server.Host.Service /// The to create with public ServerService(IWatchdogFactory watchdogFactory) { + if (watchdogFactory == null) + throw new ArgumentNullException(nameof(watchdogFactory)); ServiceName = "tgstation-server"; watchdog = watchdogFactory.CreateWatchdog(); } /// + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "cancellationTokenSource")] protected override void Dispose(bool disposing) { - cancellationTokenSource.Dispose(); + cancellationTokenSource?.Dispose(); base.Dispose(disposing); } diff --git a/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs b/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0158cb9bbb --- /dev/null +++ b/tests/Tgstation.Server.Host.Service.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Tgstation.Server.Host.Service.Tests")] +[assembly: ComVisible(false)] + +[assembly: Guid("aa80a190-52e2-4be3-bfeb-1f148d9e9007")] + +[assembly: AssemblyVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyInformationalVersion("4.0.0.0")] diff --git a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs new file mode 100644 index 0000000000..d15ad6f69e --- /dev/null +++ b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Watchdog; + +namespace Tgstation.Server.Host.Service.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestServerService + { + [TestMethod] + public void TestConstructionAndDisposal() + { + Assert.ThrowsException(() => new ServerService(null)); + var mockWatchdogFactory = new Mock(); + new ServerService(mockWatchdogFactory.Object).Dispose(); + } + + [TestMethod] + public void TestRun() + { + var type = typeof(ServerService); + var onStart = type.GetMethod("OnStart", BindingFlags.Instance | BindingFlags.NonPublic); + var onStop = type.GetMethod("OnStop", BindingFlags.Instance | BindingFlags.NonPublic); + + var mockWatchdog = new Mock(); + var args = Array.Empty(); + CancellationToken cancellationToken; + mockWatchdog.Setup(x => x.RunAsync(args, It.IsAny())).Callback((string[] _, CancellationToken token) => cancellationToken = token).Returns(Task.CompletedTask).Verifiable(); + var mockWatchdogFactory = new Mock(); + mockWatchdogFactory.Setup(x => x.CreateWatchdog()).Returns(mockWatchdog.Object).Verifiable(); + + using(var service = new ServerService(mockWatchdogFactory.Object)) + { + onStart.Invoke(service, new object[] { args }); + + Assert.IsFalse(cancellationToken.IsCancellationRequested); + + onStop.Invoke(service, Array.Empty()); + Assert.IsTrue(cancellationToken.IsCancellationRequested); + mockWatchdog.VerifyAll(); + } + + mockWatchdogFactory.VerifyAll(); + } + } +} diff --git a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj new file mode 100644 index 0000000000..fdbb9cfe04 --- /dev/null +++ b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {AA80A190-52E2-4BE3-BFEB-1F148D9E9007} + Library + Properties + Tgstation.Server.Host.Service.Tests + Tgstation.Server.Host.Service.Tests + v4.7.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll + + + ..\..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + ..\..\packages\Moq.4.8.2\lib\net45\Moq.dll + + + + + + + ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + + ..\..\packages\System.ValueTuple.4.4.0\lib\net47\System.ValueTuple.dll + True + + + + + + + + + + + + {29927416-3b78-49a7-a560-5ccaa638b6b4} + Tgstation.Server.Host.Service + + + {5D2D682C-6BF0-439C-850B-6AB945BBEAEA} + Tgstation.Server.Host.Watchdog + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Service.Tests/packages.config b/tests/Tgstation.Server.Host.Service.Tests/packages.config new file mode 100644 index 0000000000..c4f8ed9897 --- /dev/null +++ b/tests/Tgstation.Server.Host.Service.Tests/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj index 4ac1b59a9d..d69d3cbdf8 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj @@ -8,9 +8,9 @@ latest - true + false - bin\Release\netcoreapp2.0\Tgstation.Server.Host.Watchdog.Tests.xml + diff --git a/tgstation-server.sln b/tgstation-server.sln index 0aeda4e3e6..58f4e6fea3 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watch EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Startup", "src\Tgstation.Server.Host.Startup\Tgstation.Server.Host.Startup.csproj", "{7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Service.Tests", "tests\Tgstation.Server.Host.Service.Tests\Tgstation.Server.Host.Service.Tests.csproj", "{AA80A190-52E2-4BE3-BFEB-1F148D9E9007}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -129,6 +131,10 @@ Global {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Release|Any CPU.Build.0 = Release|Any CPU + {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 26591b838aa62eafc225d96fb3fe598e40f7d6c0 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 6 Apr 2018 17:38:51 -0400 Subject: [PATCH 058/397] Remove windows long filename prefix --- .../WindowsActiveAssemblyDeleter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs index 811c5bee3a..2b7db289b9 100644 --- a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs +++ b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs @@ -29,8 +29,7 @@ namespace Tgstation.Server.Host.Watchdog throw new ArgumentNullException(nameof(assemblyPath)); //Can't use Path.GetTempFileName() because it may cross drives, which won't actually rename the file - //Also append the long path prefix just in case we're running on .NET framework - var tmpLocation = String.Concat(@"\\?\", assemblyPath, Guid.NewGuid()); + var tmpLocation = String.Concat(assemblyPath, Guid.NewGuid()); File.Move(assemblyPath, tmpLocation); DeleteFileOnReboot(tmpLocation); } From 28b50a903b59cfd9ddc57d9152fe7165abd351ce Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 6 Apr 2018 17:53:51 -0400 Subject: [PATCH 059/397] Fix appveyor publish directory --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2330c6d64c..9eb5280318 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -50,7 +50,7 @@ test_script: after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 - - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o artifacts/ServerConsole -c %CONFIGURATION% + - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../artifacts/ServerConsole -c %CONFIGURATION% deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" From 6d8014dd0f0ceca740cfd1317cdd0825eab9c391 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 6 Apr 2018 18:09:40 -0400 Subject: [PATCH 060/397] One more level --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 9eb5280318..ebfe1041dc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -50,7 +50,7 @@ test_script: after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 - - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../artifacts/ServerConsole -c %CONFIGURATION% + - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" From 37f168a9d54f55001fb597b4624fe0fb831ac035 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 22:06:32 -0400 Subject: [PATCH 061/397] Fix linux docker --- .../WindowsActiveAssemblyDeleter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs index 2b7db289b9..a926cfc51d 100644 --- a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs +++ b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Watchdog [ExcludeFromCodeCoverage] static void DeleteFileOnReboot(string path) { - if (!NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) throw new Win32Exception(Marshal.GetLastWin32Error()); } From c9e71a2520f35111b242fb94ffb6999c4ec45d96 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 22:39:40 -0400 Subject: [PATCH 062/397] Use ExcludeFromCodeCoverage to get rid of some stupidness --- .../IOSIdentifier.cs | 13 ------------- .../OSIdentifier.cs | 11 ----------- .../WatchdogFactory.cs | 10 ++++++---- .../WindowsActiveAssemblyDeleter.cs | 2 +- .../TestOSIdentifier.cs | 19 ------------------- .../TestWatchdogFactory.cs | 17 +---------------- 6 files changed, 8 insertions(+), 64 deletions(-) delete mode 100644 src/Tgstation.Server.Host.Watchdog/IOSIdentifier.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/OSIdentifier.cs delete mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestOSIdentifier.cs diff --git a/src/Tgstation.Server.Host.Watchdog/IOSIdentifier.cs b/src/Tgstation.Server.Host.Watchdog/IOSIdentifier.cs deleted file mode 100644 index 46c35da7c8..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/IOSIdentifier.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// For gathering runtime OS information - /// - interface IOSIdentifier - { - /// - /// If the operating system is Win32 based - /// - bool IsWindows { get; } - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/OSIdentifier.cs b/src/Tgstation.Server.Host.Watchdog/OSIdentifier.cs deleted file mode 100644 index e5e483e3d2..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/OSIdentifier.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - sealed class OSIdentifier : IOSIdentifier - { - /// - public bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs index 6b5c9dabe3..ddb00dafe0 100644 --- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -1,11 +1,13 @@ -namespace Tgstation.Server.Host.Watchdog +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Watchdog { /// public sealed class WatchdogFactory : IWatchdogFactory { - internal static IOSIdentifier osIdentifier { get; set; } = new OSIdentifier(); - /// - public IWatchdog CreateWatchdog() => new Watchdog(new ServerFactory(), osIdentifier.IsWindows ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory()); + [ExcludeFromCodeCoverage] + public IWatchdog CreateWatchdog() => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory()); } } diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs index a926cfc51d..2b7db289b9 100644 --- a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs +++ b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Watchdog [ExcludeFromCodeCoverage] static void DeleteFileOnReboot(string path) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) + if (!NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) throw new Win32Exception(Marshal.GetLastWin32Error()); } diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestOSIdentifier.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestOSIdentifier.cs deleted file mode 100644 index 0c0cf1b9e2..0000000000 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestOSIdentifier.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Runtime.InteropServices; - -namespace Tgstation.Server.Host.Watchdog.Tests -{ - /// - /// Tests for - /// - [TestClass] - public sealed class TestOSIdentifier - { - [TestMethod] - public void TestOSIdentification() - { - var identifier = new OSIdentifier(); - Assert.AreEqual(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), identifier.IsWindows); - } - } -} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs index f5528160b9..e043a0a838 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs @@ -10,25 +10,10 @@ namespace Tgstation.Server.Host.Watchdog.Tests public sealed class TestWatchdogFactory { [TestMethod] - public void TestCreateWatchdogWindows() + public void TestCreateWatchdog() { var factory = new WatchdogFactory(); - var mockOSIdentifier = new Mock(); - mockOSIdentifier.SetupGet(x => x.IsWindows).Returns(true).Verifiable(); - WatchdogFactory.osIdentifier = mockOSIdentifier.Object; Assert.IsNotNull(factory.CreateWatchdog()); - mockOSIdentifier.VerifyAll(); - } - - [TestMethod] - public void TestCreateWatchdogNonWindows() - { - var factory = new WatchdogFactory(); - var mockOSIdentifier = new Mock(); - mockOSIdentifier.SetupGet(x => x.IsWindows).Returns(false).Verifiable(); - WatchdogFactory.osIdentifier = mockOSIdentifier.Object; - Assert.IsNotNull(factory.CreateWatchdog()); - mockOSIdentifier.VerifyAll(); } } } From 19710c6d3a41c4b68443c7972b8dd2434a00d537 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 22:51:46 -0400 Subject: [PATCH 063/397] Fucking why is this so hard --- .../WindowsActiveAssemblyDeleter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs index 2b7db289b9..a926cfc51d 100644 --- a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs +++ b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Watchdog [ExcludeFromCodeCoverage] static void DeleteFileOnReboot(string path) { - if (!NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) throw new Win32Exception(Marshal.GetLastWin32Error()); } From 267cecab916c69104bc355fbf79836567fc06194 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Apr 2018 23:52:34 -0400 Subject: [PATCH 064/397] Skeleton web app --- src/Tgstation.Server.Host.Startup/IServer.cs | 5 +- .../IServerFactory.cs | 11 +-- .../IsolatedServerFactory.cs | 5 +- .../Watchdog.cs | 29 +++---- src/Tgstation.Server.Host/Core/Application.cs | 76 +++++++++++++++++++ src/Tgstation.Server.Host/Server.cs | 25 +++++- src/Tgstation.Server.Host/ServerFactory.cs | 6 +- .../Tgstation.Server.Host.csproj | 1 + .../Tgstation.Server.Host.Tests/TestServer.cs | 19 ----- .../Tgstation.Server.Host.Tests.csproj | 5 +- .../TestIsolatedServerFactory.cs | 2 +- .../TestWatchdog.cs | 6 +- 12 files changed, 132 insertions(+), 58 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/Application.cs delete mode 100644 tests/Tgstation.Server.Host.Tests/TestServer.cs diff --git a/src/Tgstation.Server.Host.Startup/IServer.cs b/src/Tgstation.Server.Host.Startup/IServer.cs index 8dad58bdcc..28a2428211 100644 --- a/src/Tgstation.Server.Host.Startup/IServer.cs +++ b/src/Tgstation.Server.Host.Startup/IServer.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Startup /// /// Represents the host /// - public interface IServer : IDisposable + public interface IServer { /// /// The path to the updated assembly to run if any. Populated once returns @@ -17,9 +17,8 @@ namespace Tgstation.Server.Host.Startup /// /// Runs the /// - /// The arguments for the /// The for the operation /// A representing the running operation - Task RunAsync(string[] args, CancellationToken cancellationToken); + Task RunAsync(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host.Startup/IServerFactory.cs b/src/Tgstation.Server.Host.Startup/IServerFactory.cs index 43668a1049..ed92399240 100644 --- a/src/Tgstation.Server.Host.Startup/IServerFactory.cs +++ b/src/Tgstation.Server.Host.Startup/IServerFactory.cs @@ -5,10 +5,11 @@ /// public interface IServerFactory { - /// - /// Create a - /// - /// A new - IServer CreateServer(); + /// + /// Create a + /// + /// The arguments for the + /// A new + IServer CreateServer(string[] args); } } diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs index c7fe7f49cc..2545747721 100644 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs @@ -25,8 +25,9 @@ namespace Tgstation.Server.Host.Watchdog /// /// Loads the at and creates an from it /// + /// The arguments for the /// A new - public IServer CreateServer() + public IServer CreateServer(string[] args) { var assembly = LoadFromAssemblyPath(assemblyPath); //find the IServerFactory implementation @@ -35,7 +36,7 @@ namespace Tgstation.Server.Host.Watchdog var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); - return serverFactory.CreateServer(); + return serverFactory.CreateServer(args); } //honestly have no idea what this is for, but the examples i see just return null and it seems to work just fine diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 540955d094..ef74711534 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -43,24 +43,19 @@ namespace Tgstation.Server.Host.Watchdog //first run the host we started with var serverFactory = initialServerFactory; var assemblyPath = serverFactory.GetType().Assembly.Location; - do - { - string updatePath; - using (var server = serverFactory.CreateServer()) - { - serverFactory = null; - await server.RunAsync(args, cancellationToken).ConfigureAwait(false); - updatePath = server.UpdatePath; - } + do + { + var server = serverFactory.CreateServer(args); + await server.RunAsync(cancellationToken).ConfigureAwait(false); - if (updatePath == null) - break; - - activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath); - File.Move(updatePath, assemblyPath); - serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath); - } - while (!cancellationToken.IsCancellationRequested); + if (server.UpdatePath == null) + break; + + activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath); + File.Move(server.UpdatePath, assemblyPath); + serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath); + } + while (!cancellationToken.IsCancellationRequested); } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs new file mode 100644 index 0000000000..83e0a44524 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Configures the ASP.NET Core web application + /// + sealed class Application + { + /// + /// The for the + /// + readonly IConfiguration configuration; + + /// + /// Construct an + /// + /// The value of + public Application(IConfiguration configuration) => this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + + /// + /// Configure dependency injected services + /// + /// The to configure +#pragma warning disable CA1822 // Mark members as static + public void ConfigureServices(IServiceCollection services) +#pragma warning restore CA1822 // Mark members as static + { + if (services == null) + throw new ArgumentNullException(nameof(services)); + + services.AddMvc(); + services.AddOptions(); + services.AddLocalization(); + } + + /// + /// Configure the + /// + /// The to configure + /// The of the + public void Configure(IApplicationBuilder applicationBuilder, IHostingEnvironment hostingEnvironment) + { + if (applicationBuilder == null) + throw new ArgumentNullException(nameof(applicationBuilder)); + if (hostingEnvironment == null) + throw new ArgumentNullException(nameof(hostingEnvironment)); + + if (hostingEnvironment.IsDevelopment()) + applicationBuilder.UseDeveloperExceptionPage(); + + var defaultCulture = new CultureInfo("en"); + var supportedCultures = new List + { + defaultCulture + }; + + CultureInfo.CurrentCulture = defaultCulture; + CultureInfo.CurrentUICulture = defaultCulture; + + applicationBuilder.UseRequestLocalization(new RequestLocalizationOptions + { + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures, + }); + + applicationBuilder.UseMvc(); + } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 50406a988d..5de89070ff 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -1,5 +1,9 @@ -using System.Threading; +using Microsoft.AspNetCore.Hosting; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host @@ -10,10 +14,23 @@ namespace Tgstation.Server.Host /// public string UpdatePath => null; - /// - public void Dispose() { } + /// + /// The for the + /// + readonly IWebHostBuilder webHostBuilder; + + /// + /// Construct a + /// + /// The value of + public Server(IWebHostBuilder webHostBuilder) => this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder)); /// - public Task RunAsync(string[] args, CancellationToken cancellationToken) => Task.CompletedTask; + [ExcludeFromCodeCoverage] + public async Task RunAsync(CancellationToken cancellationToken) + { + using (var webHost = webHostBuilder.UseStartup().Build()) + await webHost.RunAsync(cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index e0f9291516..f934789cf6 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -1,4 +1,6 @@ -using Tgstation.Server.Host.Startup; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host { @@ -6,6 +8,6 @@ namespace Tgstation.Server.Host public sealed class ServerFactory : IServerFactory { /// - public IServer CreateServer() => new Server(); + public IServer CreateServer(string[] args) => new Server(WebHost.CreateDefaultBuilder(args)); } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index d204eda12b..9f4dc2c1ea 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -18,6 +18,7 @@ + diff --git a/tests/Tgstation.Server.Host.Tests/TestServer.cs b/tests/Tgstation.Server.Host.Tests/TestServer.cs deleted file mode 100644 index 5e20bd627e..0000000000 --- a/tests/Tgstation.Server.Host.Tests/TestServer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Tgstation.Server.Host.Tests -{ - [TestClass] - public sealed class TestServer - { - [TestMethod] - public async Task TestRunAndDispose() - { - using (var server = new Server()) - { - await server.RunAsync(null, default).ConfigureAwait(false); - Assert.IsNull(server.UpdatePath); - } - } - } -} diff --git a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj index f637b93ead..6481ec2802 100644 --- a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj +++ b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj @@ -9,8 +9,9 @@ - - + + + diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs index 33187c485b..ab382bff97 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Watchdog.Tests public void TestLoading() { var isf = new IsolatedServerFactory(typeof(ServerFactory).Assembly.Location); - Assert.IsNotNull(isf.CreateServer()); + Assert.IsNotNull(isf.CreateServer(Array.Empty())); } } } diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs index 00b492d110..5d74371f08 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs @@ -26,7 +26,7 @@ namespace Tgstation.Server.Host.Watchdog.Tests { readonly IServer server; public MockServerFactory(IServer server) => this.server = server; - public IServer CreateServer() => server; + public IServer CreateServer(string[] args) => server; } [TestMethod] @@ -41,7 +41,7 @@ namespace Tgstation.Server.Host.Watchdog.Tests using (var cts = new CancellationTokenSource()) { - mockServer.Setup(x => x.RunAsync(It.IsNotNull(), cts.Token)).Returns(Task.CompletedTask).Verifiable(); + mockServer.Setup(x => x.RunAsync(cts.Token)).Returns(Task.CompletedTask).Verifiable(); await wd.RunAsync(Array.Empty(), cts.Token).ConfigureAwait(false); mockServer.VerifyAll(); } @@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Watchdog.Tests using (var cts = new CancellationTokenSource()) { int count = 0; - mockServer.Setup(x => x.RunAsync(It.IsNotNull(), cts.Token)).Callback(() => + mockServer.Setup(x => x.RunAsync(cts.Token)).Callback(() => { if (++count > 1) cts.Cancel(); From 21ff43c1db1b56ba1f39b7872dce8c527caeeac2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 7 Apr 2018 01:00:27 -0400 Subject: [PATCH 065/397] Preliminary AuthenticationContext stuff --- src/Tgstation.Server.Api/Headers.cs | 109 ++++++++++++++++++ src/Tgstation.Server.Host/Core/Application.cs | 4 +- .../Core/ApplicationBuilderExtensions.cs | 16 +++ .../Core/AuthenticationContext.cs | 63 ++++++++++ .../Core/HttpContextExtensions.cs | 12 ++ .../Core/IAuthenticationContext.cs | 15 +++ 6 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Api/Headers.cs create mode 100644 src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs create mode 100644 src/Tgstation.Server.Host/Core/AuthenticationContext.cs create mode 100644 src/Tgstation.Server.Host/Core/HttpContextExtensions.cs create mode 100644 src/Tgstation.Server.Host/Core/IAuthenticationContext.cs diff --git a/src/Tgstation.Server.Api/Headers.cs b/src/Tgstation.Server.Api/Headers.cs new file mode 100644 index 0000000000..9f5f62955a --- /dev/null +++ b/src/Tgstation.Server.Api/Headers.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Tgstation.Server.Api +{ + public sealed class Headers + { + const string userAgentHeader = "User-Agent"; + const string apiVersionHeader = "Api-Version"; + const string tokenHeader = "Token"; + const string usernameHeader = "Username"; + const string passwordHeader = "Password"; + + public static readonly Version CurrentApiVersion = Assembly.GetExecutingAssembly().GetName().Version; + + public IReadOnlyDictionary HeaderEntries => headerEntries; + + public string UserAgent { + get => headerEntries[userAgentHeader]; + private set => headerEntries[userAgentHeader] = value; + } + + public Version ApiVersion + { + get => new Version(headerEntries[apiVersionHeader]); + private set => headerEntries[userAgentHeader] = value.ToString(); + } + + public string Token + { + get => headerEntries[tokenHeader]; + private set => headerEntries[tokenHeader] = value; + } + + public string Username + { + get => headerEntries[usernameHeader]; + private set => headerEntries[usernameHeader] = value; + } + + public string Password + { + get => headerEntries[passwordHeader]; + private set => headerEntries[passwordHeader] = value; + } + + public bool IsTokenAuthentication => headerEntries.TryGetValue(tokenHeader, out string value); + + readonly Dictionary headerEntries; + + public Headers(string userAgent, string token) : this(userAgent, token, null, null) + { + if (userAgent == null) + throw new ArgumentNullException(nameof(userAgent)); + if (token == null) + throw new ArgumentNullException(nameof(token)); + } + + public Headers(string userAgent, string username, string password) : this(userAgent, null, username, password) + { + if (userAgent == null) + throw new ArgumentNullException(nameof(userAgent)); + if (username == null) + throw new ArgumentNullException(nameof(username)); + if (password == null) + throw new ArgumentNullException(nameof(password)); + } + + public Headers(IReadOnlyDictionary headerEntries) + { + this.headerEntries = headerEntries?.ToDictionary(x => x.Key, x => x.Value) ?? throw new ArgumentNullException(nameof(headerEntries)); + AssertHeader(userAgentHeader); + AssertHeader(apiVersionHeader); + try + { + AssertHeader(usernameHeader); + AssertHeader(passwordHeader); + } + catch (InvalidOperationException) + { + AssertHeader(tokenHeader); + } + } + + void AssertHeader(string headerName) + { + try + { + var headerValue = headerEntries[headerName]; + } + catch(Exception e) + { + throw new InvalidOperationException("Missing required header!", e); + } + } + + Headers(string userAgent, string token, string username, string password) + { + headerEntries = new Dictionary(); + ApiVersion = CurrentApiVersion; + UserAgent = userAgent; + Token = token; + Username = username; + Password = password; + } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 83e0a44524..1c4cf52f9c 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -69,7 +69,9 @@ namespace Tgstation.Server.Host.Core SupportedCultures = supportedCultures, SupportedUICultures = supportedCultures, }); - + + applicationBuilder.UseSystemAuthentication(); + applicationBuilder.UseMvc(); } } diff --git a/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..a1714d6b5d --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Builder; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tgstation.Server.Host.Core +{ + static class ApplicationBuilderExtensions + { + public static IApplicationBuilder UseSystemAuthentication(this IApplicationBuilder applicationBuilder) + { + AuthenticationContext.AddToPipeline(applicationBuilder); + return applicationBuilder; + } + } +} diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs new file mode 100644 index 0000000000..6c770faa5b --- /dev/null +++ b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; +using System; +using System.Net; +using System.Linq; +using Tgstation.Server.Api; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Core +{ + sealed class AuthenticationContext : IAuthenticationContext + { + static readonly object contextKey = new object(); + + public static IAuthenticationContext Current(HttpContext httpContext) => (IAuthenticationContext)httpContext.Items[contextKey]; + + public static void AddToPipeline(IApplicationBuilder applicationBuilder) => applicationBuilder.Use(async (httpContext, next) => + { + Headers headers; + try + { + headers = new Headers(httpContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value.First())); + } + catch (InvalidOperationException) + { + httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + } + + using (var authContext = new AuthenticationContext(headers)) + { + if (!authContext.Valid) + { + httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + return; + } + httpContext.Items[contextKey] = authContext; + await next().ConfigureAwait(false); + } + }); + + public AuthenticationContext(Headers headers) + { + throw new NotImplementedException(); + } + + ~AuthenticationContext() => Dispose(); + + public void Dispose() + { + + GC.SuppressFinalize(this); + } + + public IAuthenticationContext Clone() => throw new NotImplementedException(); + + bool Valid => throw new NotImplementedException(); + + public User User => throw new NotImplementedException(); + + public InstanceUser InstanceUser => throw new NotImplementedException(); + } +} diff --git a/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs b/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs new file mode 100644 index 0000000000..7bca0daf5b --- /dev/null +++ b/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tgstation.Server.Host.Core +{ + static class HttpContextExtensions + { + public static IAuthenticationContext AuthenticationContext(this HttpContext httpContext) => Core.AuthenticationContext.Current(httpContext); + } +} diff --git a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs new file mode 100644 index 0000000000..dacb668c86 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Core +{ + interface IAuthenticationContext : IDisposable + { + User User { get; } + + InstanceUser InstanceUser { get; } + + IAuthenticationContext Clone(); + } +} \ No newline at end of file From 34d610a33666bfd840736e40b29ecf7c137c0108 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 7 Apr 2018 10:13:55 -0400 Subject: [PATCH 066/397] Commit early, commit often --- src/Tgstation.Server.Api/ModelAttribute.cs | 2 +- src/Tgstation.Server.Api/Models/Instance.cs | 2 +- src/Tgstation.Server.Api/Models/User.cs | 2 +- .../Core/AuthenticationContext.cs | 128 ++++++++++++------ .../Core/IAuthenticationContext.cs | 28 +++- .../Core/ISystemIdentity.cs | 14 ++ .../Core/ISystemIdentityFactory.cs | 10 ++ .../Core/ITokenManager.cs | 15 ++ .../Models/IDatabaseContext.cs | 20 +++ src/Tgstation.Server.Host/Models/Instance.cs | 16 +++ src/Tgstation.Server.Host/Models/TestMerge.cs | 41 ++++++ src/Tgstation.Server.Host/Models/User.cs | 21 +++ .../Tgstation.Server.Host.csproj | 3 + 13 files changed, 255 insertions(+), 47 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/ISystemIdentity.cs create mode 100644 src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs create mode 100644 src/Tgstation.Server.Host/Core/ITokenManager.cs create mode 100644 src/Tgstation.Server.Host/Models/IDatabaseContext.cs create mode 100644 src/Tgstation.Server.Host/Models/Instance.cs create mode 100644 src/Tgstation.Server.Host/Models/TestMerge.cs create mode 100644 src/Tgstation.Server.Host/Models/User.cs diff --git a/src/Tgstation.Server.Api/ModelAttribute.cs b/src/Tgstation.Server.Api/ModelAttribute.cs index 6145721634..df50284cf3 100644 --- a/src/Tgstation.Server.Api/ModelAttribute.cs +++ b/src/Tgstation.Server.Api/ModelAttribute.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api /// /// Indicates the rights for a model /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public sealed class ModelAttribute : Attribute { /// diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 43d3eea404..276eed69e3 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// Metadata about a server instance /// [Model(RightsType.InstanceManager, CanCrud = true, CanList = true)] - public sealed class Instance + public class Instance { /// /// The id of the . Not modifiable diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index fe50e0bc77..e53f42e419 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// Represents a server /// [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers)] - public sealed class User + public class User { /// /// The ID of the diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs index 6c770faa5b..6418d3be78 100644 --- a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs @@ -1,63 +1,113 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; using System; using System.Net; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Api; -using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { + /// + /// Manages s for a scope + /// sealed class AuthenticationContext : IAuthenticationContext { + /// + /// Used to store the in + /// static readonly object contextKey = new object(); + /// + /// Get the current from an + /// + /// The containing the + /// The in public static IAuthenticationContext Current(HttpContext httpContext) => (IAuthenticationContext)httpContext.Items[contextKey]; - public static void AddToPipeline(IApplicationBuilder applicationBuilder) => applicationBuilder.Use(async (httpContext, next) => + /// + /// Adds handling to an pipeline + /// + /// The to add to + public static void AddToPipeline(IApplicationBuilder applicationBuilder) + { + applicationBuilder.Use(async (httpContext, next) => + { + Headers headers; + try + { + headers = new Headers(httpContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value.First())); + } + catch (InvalidOperationException) + { + httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + } + + ISystemIdentity systemIdentity; + + var systemIdentityFactory = applicationBuilder.ApplicationServices.GetRequiredService(); + var tokenManager = applicationBuilder.ApplicationServices.GetRequiredService(); + var databaseContext = applicationBuilder.ApplicationServices.GetRequiredService(); + try + { + if (headers.IsTokenAuthentication) + { + var user = await tokenManager.GetUser(headers.Token, httpContext.RequestAborted).ConfigureAwait(false); + systemIdentity = systemIdentityFactory.CreateSystemIdentity(user); + } + else + systemIdentity = systemIdentityFactory.CreateSystemIdentity(headers.Username, headers.Password); + } + catch (UnauthorizedAccessException) + { + httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + return; + } + + using (var authContext = new AuthenticationContext(systemIdentity, databaseContext)) + { + httpContext.Items[contextKey] = authContext; + await next().ConfigureAwait(false); + } + }); + } + + /// + public ISystemIdentity SystemIdentity { get; } + + /// + /// The for the + /// + readonly IDatabaseContext databaseContext; + + /// + /// Construct a + /// + /// The value of + /// The value of + public AuthenticationContext(ISystemIdentity systemIdentity, IDatabaseContext databaseContext) { - Headers headers; - try - { - headers = new Headers(httpContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value.First())); - } - catch (InvalidOperationException) - { - httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - return; - } + SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity)); + this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + } - using (var authContext = new AuthenticationContext(headers)) - { - if (!authContext.Valid) - { - httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - return; - } - httpContext.Items[contextKey] = authContext; - await next().ConfigureAwait(false); - } - }); + /// + public void Dispose() => SystemIdentity.Dispose(); - public AuthenticationContext(Headers headers) + /// + public Task User(CancellationToken cancellationToken) { throw new NotImplementedException(); } - ~AuthenticationContext() => Dispose(); - - public void Dispose() - { - - GC.SuppressFinalize(this); - } - - public IAuthenticationContext Clone() => throw new NotImplementedException(); - - bool Valid => throw new NotImplementedException(); - - public User User => throw new NotImplementedException(); - - public InstanceUser InstanceUser => throw new NotImplementedException(); + /// + public Task InstanceUser(Instance instance, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } } diff --git a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs index dacb668c86..1d2466bbaf 100644 --- a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs @@ -1,15 +1,33 @@ using System; -using System.Collections.Generic; -using Tgstation.Server.Api.Models; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { + /// + /// Represents the currently authenticated + /// interface IAuthenticationContext : IDisposable { - User User { get; } + /// + /// The of + /// + ISystemIdentity SystemIdentity { get; } - InstanceUser InstanceUser { get; } + /// + /// The represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task User(CancellationToken cancellationToken); - IAuthenticationContext Clone(); + /// + /// The represented by and a given + /// + /// The of the + /// The for the operation + /// A resulting in the represented by and a given + Task InstanceUser(Instance instance, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/ISystemIdentity.cs b/src/Tgstation.Server.Host/Core/ISystemIdentity.cs new file mode 100644 index 0000000000..6f60a6bc99 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ISystemIdentity.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Tgstation.Server.Host.Core +{ + interface ISystemIdentity : IDisposable + { + string Uid { get; } + string Username { get; } + IEnumerable Groups { get; } + + ISystemIdentity Clone(); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs new file mode 100644 index 0000000000..05e03ea829 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs @@ -0,0 +1,10 @@ +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Core +{ + interface ISystemIdentityFactory + { + ISystemIdentity CreateSystemIdentity(User user); + ISystemIdentity CreateSystemIdentity(string username, string password); + } +} diff --git a/src/Tgstation.Server.Host/Core/ITokenManager.cs b/src/Tgstation.Server.Host/Core/ITokenManager.cs new file mode 100644 index 0000000000..e898760f51 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ITokenManager.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Core +{ + interface ITokenManager + { + Task GetUser(string token, CancellationToken cancellationToken); + Task CreateToken(User user, CancellationToken cancellationToken); + Task> UserTokens(User user, CancellationToken cancellationToken); + Task> AllTokens(CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs new file mode 100644 index 0000000000..7b44a65ca6 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represents the database + /// + interface IDatabaseContext + { + DbSet Users { get; } + + DbSet Instances { get; } + + Task Save(CancellationToken cancellationToken); + + Task Initialize(CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs new file mode 100644 index 0000000000..573aa46e45 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represents an in the database + /// + sealed class Instance : Api.Models.Instance + { + /// + /// The s in the + /// + public List InstanceUsers { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs new file mode 100644 index 0000000000..7ecbf704cc --- /dev/null +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represent a merge of a live pull request + /// + sealed class TestMerge + { + long Id { get; set; } + + int Number { get; set; } + + [Required] + string OriginCommit { get; set; } + + [Required] + string PullRequestCommit { get; set; } + + string MergeCommit { get; set; } + + [Required] + DateTimeOffset MergedAt { get; set; } + + [Required] + DateTimeOffset RemovedAt { get; set; } + + [Required] + User MergedBy { get; set; } + + [Required] + string TitleAtMerge { get; set; } + + [Required] + string BodyAtMerge { get; set; } + + [Required] + string Author { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs new file mode 100644 index 0000000000..743ee5e8db --- /dev/null +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represents a in the database + /// + sealed class User : Api.Models.User + { + /// + /// The s for the + /// + List InstanceUsers { get; set; } + + /// + /// The s for the + /// + List Tokens { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 9f4dc2c1ea..40ab8fa5f7 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -19,7 +19,10 @@ + + + From c3ca2991398c926fb945fc429635edbe3763350c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 7 Apr 2018 11:19:04 -0400 Subject: [PATCH 067/397] Gooey model flesh --- .../Models/{Chat.cs => ChatSettings.cs} | 2 +- src/Tgstation.Server.Api/Models/CompileJob.cs | 55 +++++++++++++++++++ src/Tgstation.Server.Api/Models/DreamMaker.cs | 39 ++----------- src/Tgstation.Server.Api/Models/Instance.cs | 10 +++- src/Tgstation.Server.Api/Models/Repository.cs | 9 ++- src/Tgstation.Server.Api/Models/TestMerge.cs | 32 +++++++++++ .../Models/TestMergeParameters.cs | 12 ++++ src/Tgstation.Server.Api/Models/Token.cs | 10 ++++ src/Tgstation.Server.Api/Rights/ChatRights.cs | 2 +- .../Rights/InstanceManagerRights.cs | 6 +- .../Tgstation.Server.Api.csproj | 6 ++ .../Components/IChatClient.cs | 12 ++-- .../Components/IDreamMakerClient.cs | 2 +- .../Models/ChatSettings.cs | 9 +++ .../Models/CompileJob.cs | 7 +++ src/Tgstation.Server.Host/Models/Instance.cs | 15 +++++ src/Tgstation.Server.Host/Models/TestMerge.cs | 31 +---------- 17 files changed, 181 insertions(+), 78 deletions(-) rename src/Tgstation.Server.Api/Models/{Chat.cs => ChatSettings.cs} (98%) create mode 100644 src/Tgstation.Server.Api/Models/CompileJob.cs create mode 100644 src/Tgstation.Server.Api/Models/TestMerge.cs create mode 100644 src/Tgstation.Server.Api/Models/TestMergeParameters.cs create mode 100644 src/Tgstation.Server.Host/Models/ChatSettings.cs create mode 100644 src/Tgstation.Server.Host/Models/CompileJob.cs diff --git a/src/Tgstation.Server.Api/Models/Chat.cs b/src/Tgstation.Server.Api/Models/ChatSettings.cs similarity index 98% rename from src/Tgstation.Server.Api/Models/Chat.cs rename to src/Tgstation.Server.Api/Models/ChatSettings.cs index 543eae1230..6400178619 100644 --- a/src/Tgstation.Server.Api/Models/Chat.cs +++ b/src/Tgstation.Server.Api/Models/ChatSettings.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Api.Models /// Manage the server chat bots /// [Model(RightsType.Chat, RequiresInstance = true)] - public sealed class Chat + public class ChatSettings { /// /// If the IRC client is enabled diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs new file mode 100644 index 0000000000..164bc777ec --- /dev/null +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -0,0 +1,55 @@ +using System; +using System.ComponentModel.DataAnnotations; +namespace Tgstation.Server.Api.Models +{ + public class CompileJob + { + /// + /// The ID of the job + /// + public long Id { get; set; } + + /// + /// The that triggered the job + /// + public User TriggeredBy { get; set; } + + /// + /// When the compilation started + /// + [Required] + public DateTimeOffset StartedAt { get; set; } + + /// + /// When the compilation finished + /// + public DateTimeOffset FinishedAt { get; set; } + + /// + /// If the compiler targeted the primary directory + /// + public bool? TargetedPrimaryDirectory { get; set; } + + /// + /// Textual output of DM + /// + public string Output { get; set; } + + /// + /// Exit code of DM. If , the job was cancelled + /// + public int? ExitCode { get; set; } + + /// + /// Git revision the compiler ran on. Not modifiable + /// + [Required] + public string Revision { get; set; } + + /// + /// Git revision of the origin branch the compiler ran on. Not modifiable + /// + [Required] + public string OriginRevision { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index e8a093f5a6..cbc325ab97 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -1,5 +1,4 @@ -using System; -using Tgstation.Server.Api.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { @@ -10,45 +9,15 @@ namespace Tgstation.Server.Api.Models public sealed class DreamMaker { /// - /// When the compilation started + /// The last ran /// - [Permissions(DenyWrite = true)] - public DateTimeOffset StartedAt { get; set; } - /// - /// When the compilation finished - /// - [Permissions(DenyWrite = true)] - public DateTimeOffset FinishedAt { get; set; } + public CompileJob LastJob { get; set; } + /// /// The of the compiler /// [Permissions(DenyWrite = true)] public CompilerStatus Status { get; set; } - /// - /// If the compiler targeted the primary directory - /// - [Permissions(DenyWrite = true)] - public bool? TargetedPrimaryDirectory { get; set; } - /// - /// Textual output of DM - /// - [Permissions(DenyWrite = true)] - public string Output { get; set; } - /// - /// Exit code of DM - /// - [Permissions(DenyWrite = true)] - public int? ExitCode { get; set; } - /// - /// Git revision the compiler ran on. Not modifiable - /// - [Permissions(DenyWrite = true)] - public string Revision { get; set; } - /// - /// Git revision of the origin branch the compiler ran on. Not modifiable - /// - [Permissions(DenyWrite = true)] - public string OriginRevision { get; set; } /// /// How often the automatically compiles in minutes diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 276eed69e3..717efc7505 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { @@ -18,6 +19,7 @@ namespace Tgstation.Server.Api.Models /// The name of the /// [Permissions(WriteRight = InstanceManagerRights.Rename)] + [Required] public string Name { get; set; } /// @@ -31,5 +33,11 @@ namespace Tgstation.Server.Api.Models /// [Permissions(WriteRight = InstanceManagerRights.SetOnline)] public bool Online { get; set; } + + /// + /// If can be used on the + /// + [Permissions(WriteRight = InstanceManagerRights.SetConfiguration)] + public bool ConfigurationAllowed { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 57c1a14f7d..8876ce73e9 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -46,11 +46,14 @@ namespace Tgstation.Server.Api.Models public IReadOnlyDictionary Backups { get; set; } /// - /// Associated list of GitHub pull request number -> sha for merged pull requests. Adding a value to this list will merge the latest commit of the pull request numbered by the key + /// List of s in the repository /// - [Permissions(WriteRight = RepositoryRights.MergePullRequest)] - public Dictionary PullRequests { get; set; } + [Permissions(DenyWrite = true)] + public List TestMerges { get; set; } + [Permissions(WriteRight = RepositoryRights.MergePullRequest)] + public List NewTestMerges { get; set; } + /// /// The username to access the git repository with /// diff --git a/src/Tgstation.Server.Api/Models/TestMerge.cs b/src/Tgstation.Server.Api/Models/TestMerge.cs new file mode 100644 index 0000000000..9a88466f9c --- /dev/null +++ b/src/Tgstation.Server.Api/Models/TestMerge.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models +{ + public class TestMerge : TestMergeParameters + { + public long Id { get; set; } + + [Required] + public string OriginCommit { get; set; } + + public string MergeCommit { get; set; } + + [Required] + public DateTimeOffset MergedAt { get; set; } + + [Required] + public DateTimeOffset RemovedAt { get; set; } + + public User MergedBy { get; set; } + + [Required] + public string TitleAtMerge { get; set; } + + [Required] + public string BodyAtMerge { get; set; } + + [Required] + public string Author { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs new file mode 100644 index 0000000000..542dbb6a7d --- /dev/null +++ b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models +{ + public class TestMergeParameters + { + public int Number { get; set; } + + [Required] + public string Commit { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index e970114954..dffa764376 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using System.Net; using Tgstation.Server.Api.Rights; @@ -18,21 +19,30 @@ namespace Tgstation.Server.Api.Models /// /// The user agent that created the /// + [Required] public string ClientUserAgent { get; set; } /// /// The the was originally issued to /// + [Required] public IPAddress IssuedTo { get; set; } /// /// When the was originally issued /// + [Required] public DateTimeOffset IssuedAt { get; set; } + /// + /// When the was last used + /// + public DateTimeOffset LastUsedAt { get; set; } + /// /// The token . Not modifiable, only appears once /// + [Required] public string Value { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/ChatRights.cs b/src/Tgstation.Server.Api/Rights/ChatRights.cs index 30fb36787a..59f3720478 100644 --- a/src/Tgstation.Server.Api/Rights/ChatRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatRights.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Api.Rights { /// - /// Rights for + /// Rights for /// [Flags] public enum ChatRights diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index a00d4ffedc..106eaa1ad1 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -39,6 +39,10 @@ namespace Tgstation.Server.Api.Rights /// /// User can view all s /// - List = 64 + List = 64, + /// + /// User can change + /// + SetConfiguration = 128 } } diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 942c0a5227..2e3f40a448 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -22,4 +22,10 @@ + + + ..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\system.componentmodel.annotations\4.4.0\ref\netstandard2.0\System.ComponentModel.Annotations.dll + + + diff --git a/src/Tgstation.Server.Client/Components/IChatClient.cs b/src/Tgstation.Server.Client/Components/IChatClient.cs index 4699395a6f..2a6f0f6e3a 100644 --- a/src/Tgstation.Server.Client/Components/IChatClient.cs +++ b/src/Tgstation.Server.Client/Components/IChatClient.cs @@ -11,18 +11,18 @@ namespace Tgstation.Server.Client.Components public interface IChatClient : IRightsClient { /// - /// Get the represented by the + /// Get the represented by the /// /// The for the operation - /// A resulting in the represented by the - Task Read(CancellationToken cancellationToken); + /// A resulting in the represented by the + Task Read(CancellationToken cancellationToken); /// - /// Updates the setttings + /// Updates the setttings /// - /// The to update + /// The to update /// The for the operation /// A representing the running operation - Task Update(Chat chat, CancellationToken cancellationToken); + Task Update(ChatSettings chat, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs b/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs index 276f1d2258..ef1fb7b29b 100644 --- a/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamMakerClient.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Client.Components /// /// The for the operation /// A resulting in the represented by the - Task Read(CancellationToken cancellationToken); + Task Read(CancellationToken cancellationToken); /// /// Updates the setttings diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs new file mode 100644 index 0000000000..b85f452f5d --- /dev/null +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -0,0 +1,9 @@ +namespace Tgstation.Server.Host.Models +{ + sealed class ChatSettings : Api.Models.ChatSettings + { + public long Id { get; set; } + + public long InstanceId { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs new file mode 100644 index 0000000000..abe4ab2a74 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -0,0 +1,7 @@ +namespace Tgstation.Server.Host.Models +{ + sealed class CompileJob : Api.Models.CompileJob + { + new User TriggeredBy { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 573aa46e45..33e0dacf27 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -8,9 +8,24 @@ namespace Tgstation.Server.Host.Models /// sealed class Instance : Api.Models.Instance { + /// + /// The for the + /// + ChatSettings ChatSettings { get; set; } + /// /// The s in the /// public List InstanceUsers { get; set; } + + /// + /// The s in the + /// + public List TestMerges { get; set; } + + /// + /// The s in the + /// + public List CompileJobs { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index 7ecbf704cc..8722514f1a 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -6,36 +6,9 @@ namespace Tgstation.Server.Host.Models /// /// Represent a merge of a live pull request /// - sealed class TestMerge + sealed class TestMerge : Api.Models.TestMerge { - long Id { get; set; } - - int Number { get; set; } - [Required] - string OriginCommit { get; set; } - - [Required] - string PullRequestCommit { get; set; } - - string MergeCommit { get; set; } - - [Required] - DateTimeOffset MergedAt { get; set; } - - [Required] - DateTimeOffset RemovedAt { get; set; } - - [Required] - User MergedBy { get; set; } - - [Required] - string TitleAtMerge { get; set; } - - [Required] - string BodyAtMerge { get; set; } - - [Required] - string Author { get; set; } + new User MergedBy { get; set; } } } From 394afb4b30acadc01f3db06d6c553c06947b0d83 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 7 Apr 2018 11:57:59 -0400 Subject: [PATCH 068/397] Complex but consistent --- .../{Headers.cs => ApiHeaders.cs} | 10 +++++----- .../Models/ChatSettings.cs | 20 +++++++++---------- src/Tgstation.Server.Api/Models/CompileJob.cs | 2 +- .../Models/DreamDaemon.cs | 20 +------------------ src/Tgstation.Server.Api/Models/DreamMaker.cs | 17 +++------------- .../Models/InstanceUser.cs | 4 ++-- .../Models/Internal/DreamMakerSettings.cs | 19 ++++++++++++++++++ src/Tgstation.Server.Api/Models/Repository.cs | 11 ++++------ .../Models/RevisionInformation.cs | 16 +++++++++++++++ src/Tgstation.Server.Api/Models/TestMerge.cs | 4 +--- .../Models/TestMergeParameters.cs | 2 +- .../{ChatRights.cs => ChatSettingsRights.cs} | 2 +- .../Rights/RightsHelper.cs | 2 +- src/Tgstation.Server.Api/Rights/RightsType.cs | 2 +- ...{IChatClient.cs => IChatSettingsClient.cs} | 6 +++--- .../Components/IInstanceClient.cs | 4 ++-- .../Core/AuthenticationContext.cs | 4 ++-- .../Models/ChatSettings.cs | 9 +++++++-- .../Models/CompileJob.cs | 5 ++++- .../Models/DreamMakerSettings.cs | 12 +++++++++++ src/Tgstation.Server.Host/Models/Instance.cs | 10 +++++++++- src/Tgstation.Server.Host/Models/TestMerge.cs | 3 +-- 22 files changed, 106 insertions(+), 78 deletions(-) rename src/Tgstation.Server.Api/{Headers.cs => ApiHeaders.cs} (88%) create mode 100644 src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs create mode 100644 src/Tgstation.Server.Api/Models/RevisionInformation.cs rename src/Tgstation.Server.Api/Rights/{ChatRights.cs => ChatSettingsRights.cs} (96%) rename src/Tgstation.Server.Client/Components/{IChatClient.cs => IChatSettingsClient.cs} (83%) create mode 100644 src/Tgstation.Server.Host/Models/DreamMakerSettings.cs diff --git a/src/Tgstation.Server.Api/Headers.cs b/src/Tgstation.Server.Api/ApiHeaders.cs similarity index 88% rename from src/Tgstation.Server.Api/Headers.cs rename to src/Tgstation.Server.Api/ApiHeaders.cs index 9f5f62955a..894aaa4650 100644 --- a/src/Tgstation.Server.Api/Headers.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -5,7 +5,7 @@ using System.Reflection; namespace Tgstation.Server.Api { - public sealed class Headers + public sealed class ApiHeaders { const string userAgentHeader = "User-Agent"; const string apiVersionHeader = "Api-Version"; @@ -50,7 +50,7 @@ namespace Tgstation.Server.Api readonly Dictionary headerEntries; - public Headers(string userAgent, string token) : this(userAgent, token, null, null) + public ApiHeaders(string userAgent, string token) : this(userAgent, token, null, null) { if (userAgent == null) throw new ArgumentNullException(nameof(userAgent)); @@ -58,7 +58,7 @@ namespace Tgstation.Server.Api throw new ArgumentNullException(nameof(token)); } - public Headers(string userAgent, string username, string password) : this(userAgent, null, username, password) + public ApiHeaders(string userAgent, string username, string password) : this(userAgent, null, username, password) { if (userAgent == null) throw new ArgumentNullException(nameof(userAgent)); @@ -68,7 +68,7 @@ namespace Tgstation.Server.Api throw new ArgumentNullException(nameof(password)); } - public Headers(IReadOnlyDictionary headerEntries) + public ApiHeaders(IReadOnlyDictionary headerEntries) { this.headerEntries = headerEntries?.ToDictionary(x => x.Key, x => x.Value) ?? throw new ArgumentNullException(nameof(headerEntries)); AssertHeader(userAgentHeader); @@ -96,7 +96,7 @@ namespace Tgstation.Server.Api } } - Headers(string userAgent, string token, string username, string password) + ApiHeaders(string userAgent, string token, string username, string password) { headerEntries = new Dictionary(); ApiVersion = CurrentApiVersion; diff --git a/src/Tgstation.Server.Api/Models/ChatSettings.cs b/src/Tgstation.Server.Api/Models/ChatSettings.cs index 6400178619..a528c1d773 100644 --- a/src/Tgstation.Server.Api/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/ChatSettings.cs @@ -12,60 +12,60 @@ namespace Tgstation.Server.Api.Models /// /// If the IRC client is enabled /// - [Permissions(WriteRight = ChatRights.SetIrcEnabled)] + [Permissions(WriteRight = ChatSettingsRights.SetIrcEnabled)] public bool IrcEnabled { get; set; } /// /// The IRC server name /// - [Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)] + [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] public string IrcHost { get; set; } /// /// The IRC server port /// - [Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)] + [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] public ushort IrcPort { get; set; } /// /// The IRC server NickServ password /// - [Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)] + [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] public string IrcNickServPassword { get; set; } /// /// Channels the IRC client should join/listen/announce in and allow admin commands /// - [Permissions(WriteRight = ChatRights.SetIrcChannels)] + [Permissions(WriteRight = ChatSettingsRights.SetIrcChannels)] public List IrcAdminChannels { get; set; } /// /// Channels the IRC client should join/listen/announce in /// - [Permissions(WriteRight = ChatRights.SetIrcChannels)] + [Permissions(WriteRight = ChatSettingsRights.SetIrcChannels)] public List IrcGeneralChannels { get; set; } /// /// If the Discord bot is enabled /// - [Permissions(WriteRight = ChatRights.SetDiscordEnabled)] + [Permissions(WriteRight = ChatSettingsRights.SetDiscordEnabled)] public bool DiscordEnabled { get; set; } /// /// The Discord bot token /// - [Permissions(ReadRight = ChatRights.SetDiscordSettings, WriteRight = ChatRights.SetDiscordSettings)] + [Permissions(ReadRight = ChatSettingsRights.SetDiscordSettings, WriteRight = ChatSettingsRights.SetDiscordSettings)] public string DiscordBotToken { get; set; } /// /// Channels the Discord bot should listen/announce in and allow admin commands /// - [Permissions(WriteRight = ChatRights.SetDiscordChannels)] + [Permissions(WriteRight = ChatSettingsRights.SetDiscordChannels)] public List DiscordAdminChannels { get; set; } /// /// Channels the Discord bot should listen/announce in /// - [Permissions(WriteRight = ChatRights.SetDiscordChannels)] + [Permissions(WriteRight = ChatSettingsRights.SetDiscordChannels)] public List DiscordGeneralChannels { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 164bc777ec..1133d86e50 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -44,7 +44,7 @@ namespace Tgstation.Server.Api.Models /// Git revision the compiler ran on. Not modifiable /// [Required] - public string Revision { get; set; } + public RevisionInformation RevisionInformation { get; set; } /// /// Git revision of the origin branch the compiler ran on. Not modifiable diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 652256ef5b..6039fb4f34 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -10,29 +10,11 @@ namespace Tgstation.Server.Api.Models [Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)] public sealed class DreamDaemon { - /// - /// The pull requests merged on the live game version - /// - [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] - public IReadOnlyDictionary PullRequests { get; set; } - - /// - /// The git sha the live game version was compiled at - /// - [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] - public string Sha { get; set; } - - /// - /// The git sha of the origin branch the live game version was compiled at - /// - [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] - public string OriginSha { get; set; } - /// /// When the live revision was compiled /// [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] - public DateTimeOffset CompiledAt { get; set; } + public CompileJob CompileJob { get; set; } /// /// If starts when it's starts diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index cbc325ab97..6864a7e4aa 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { @@ -6,7 +7,7 @@ namespace Tgstation.Server.Api.Models /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile /// [Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)] - public sealed class DreamMaker + public sealed class DreamMaker : DreamMakerSettings { /// /// The last ran @@ -18,17 +19,5 @@ namespace Tgstation.Server.Api.Models /// [Permissions(DenyWrite = true)] public CompilerStatus Status { get; set; } - - /// - /// How often the automatically compiles in minutes - /// - [Permissions(WriteRight = DreamMakerRights.SetAutoCompile)] - public int? AutoCompileInterval { get; set; } - - /// - /// The .dme file tries to compile with - /// - [Permissions(WriteRight = DreamMakerRights.SetDme)] - public string TargetDme { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index a3e31e1dad..0dd9c3ca9e 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -35,9 +35,9 @@ namespace Tgstation.Server.Api.Models public RepositoryRights RepositoryRights { get; set; } /// - /// The of the + /// The of the /// - public ChatRights ChatRights { get; set; } + public ChatSettingsRights ChatRights { get; set; } /// /// The of the diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs new file mode 100644 index 0000000000..f1c826ec03 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs @@ -0,0 +1,19 @@ +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + public class DreamMakerSettings + { + /// + /// How often the automatically compiles in minutes + /// + [Permissions(WriteRight = DreamMakerRights.SetAutoCompile)] + public int? AutoCompileInterval { get; set; } + + /// + /// The .dme file tries to compile with + /// + [Permissions(WriteRight = DreamMakerRights.SetDme)] + public string TargetDme { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 8876ce73e9..eca73cace8 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -15,11 +15,14 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = RepositoryRights.SetOrigin)] public string Origin { get; set; } + [Permissions(DenyWrite = true)] + public RevisionInformation RevisionInformation { get; set; } + /// /// The commit HEAD points to /// [Permissions(WriteRight = RepositoryRights.SetSha)] - public string Sha { get; set; } + public string NewRevision { get; set; } /// /// The branch or tag HEAD points to @@ -39,12 +42,6 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = RepositoryRights.ChangeCommitter)] public string CommitterEmail { get; set; } - /// - /// Associated list of tag name -> sha for repository compiles. Not modifiable - /// - [Permissions(DenyWrite = true)] - public IReadOnlyDictionary Backups { get; set; } - /// /// List of s in the repository /// diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs new file mode 100644 index 0000000000..e3724896c6 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models +{ + public sealed class RevisionInformation + { + [Key] + string Revision { get; set; } + + [Required] + string OriginRevision { get; set; } + + List TestMerges { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/TestMerge.cs b/src/Tgstation.Server.Api/Models/TestMerge.cs index 9a88466f9c..93724a46fa 100644 --- a/src/Tgstation.Server.Api/Models/TestMerge.cs +++ b/src/Tgstation.Server.Api/Models/TestMerge.cs @@ -8,9 +8,7 @@ namespace Tgstation.Server.Api.Models public long Id { get; set; } [Required] - public string OriginCommit { get; set; } - - public string MergeCommit { get; set; } + RevisionInformation RevisionInformation { get; set; } [Required] public DateTimeOffset MergedAt { get; set; } diff --git a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs index 542dbb6a7d..06d5f7ebaf 100644 --- a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs +++ b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs @@ -7,6 +7,6 @@ namespace Tgstation.Server.Api.Models public int Number { get; set; } [Required] - public string Commit { get; set; } + public string PullRequestRevision { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/ChatRights.cs b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs similarity index 96% rename from src/Tgstation.Server.Api/Rights/ChatRights.cs rename to src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs index 59f3720478..cade0e977e 100644 --- a/src/Tgstation.Server.Api/Rights/ChatRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights /// Rights for /// [Flags] - public enum ChatRights + public enum ChatSettingsRights { /// /// User has no rights diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index 1dcefa440e..0fc6e5efcc 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Api.Rights { RightsType.Byond, typeof(ByondRights) }, { RightsType.DreamMaker, typeof(DreamMakerRights) }, { RightsType.DreamDaemon, typeof(DreamDaemonRights) }, - { RightsType.Chat, typeof(ChatRights) }, + { RightsType.Chat, typeof(ChatSettingsRights) }, { RightsType.Configuration, typeof(ConfigurationRights) }, { RightsType.InstanceUser, typeof(InstanceUserRights) } }; diff --git a/src/Tgstation.Server.Api/Rights/RightsType.cs b/src/Tgstation.Server.Api/Rights/RightsType.cs index b2659f8817..a15d9d2d03 100644 --- a/src/Tgstation.Server.Api/Rights/RightsType.cs +++ b/src/Tgstation.Server.Api/Rights/RightsType.cs @@ -35,7 +35,7 @@ /// DreamDaemon, /// - /// + /// /// Chat, /// diff --git a/src/Tgstation.Server.Client/Components/IChatClient.cs b/src/Tgstation.Server.Client/Components/IChatSettingsClient.cs similarity index 83% rename from src/Tgstation.Server.Client/Components/IChatClient.cs rename to src/Tgstation.Server.Client/Components/IChatSettingsClient.cs index 2a6f0f6e3a..d8ad100b18 100644 --- a/src/Tgstation.Server.Client/Components/IChatClient.cs +++ b/src/Tgstation.Server.Client/Components/IChatSettingsClient.cs @@ -8,13 +8,13 @@ namespace Tgstation.Server.Client.Components /// /// For managing the chat bots /// - public interface IChatClient : IRightsClient + public interface IChatSettingsClient : IRightsClient { /// - /// Get the represented by the + /// Get the represented by the /// /// The for the operation - /// A resulting in the represented by the + /// A resulting in the represented by the Task Read(CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Client/Components/IInstanceClient.cs b/src/Tgstation.Server.Client/Components/IInstanceClient.cs index baebd33d91..36b5b605f5 100644 --- a/src/Tgstation.Server.Client/Components/IInstanceClient.cs +++ b/src/Tgstation.Server.Client/Components/IInstanceClient.cs @@ -35,9 +35,9 @@ namespace Tgstation.Server.Client.Components IInstanceUserClient Users { get; } /// - /// Access the + /// Access the /// - IChatClient Chat { get; } + IChatSettingsClient Chat { get; } /// /// Access the diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs index 6418d3be78..aa8235f1b8 100644 --- a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs @@ -36,10 +36,10 @@ namespace Tgstation.Server.Host.Core { applicationBuilder.Use(async (httpContext, next) => { - Headers headers; + ApiHeaders headers; try { - headers = new Headers(httpContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value.First())); + headers = new ApiHeaders(httpContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value.First())); } catch (InvalidOperationException) { diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs index b85f452f5d..4c7ed23a76 100644 --- a/src/Tgstation.Server.Host/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -1,9 +1,14 @@ -namespace Tgstation.Server.Host.Models +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models { sealed class ChatSettings : Api.Models.ChatSettings { public long Id { get; set; } - + public long InstanceId { get; set; } + + [Required] + public Instance Instance { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index abe4ab2a74..5d1b7b93cb 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -1,7 +1,10 @@ -namespace Tgstation.Server.Host.Models +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models { sealed class CompileJob : Api.Models.CompileJob { + [Required] new User TriggeredBy { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs new file mode 100644 index 0000000000..eaddc22b8a --- /dev/null +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings + { + long Id { get; set; } + long InstanceId { get; set; } + [Required] + Instance Instance { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 33e0dacf27..174bf3d36f 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models @@ -9,9 +10,16 @@ namespace Tgstation.Server.Host.Models sealed class Instance : Api.Models.Instance { /// - /// The for the + /// The for the /// + [Required] ChatSettings ChatSettings { get; set; } + + /// + /// The for the + /// + [Required] + DreamMakerSettings DreamMakerSettings { get; set; } /// /// The s in the diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index 8722514f1a..d542473e5a 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -1,5 +1,4 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Host.Models { From 970ace54a60ffeda67481da27d93c75a30e583e3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 7 Apr 2018 13:05:39 -0400 Subject: [PATCH 069/397] More model shennanigans --- .../Models/DreamDaemon.cs | 54 ++-------------- .../Models/Internal/DreamDaemonSettings.cs | 63 +++++++++++++++++++ .../Models/Internal/Job.cs | 37 +++++++++++ src/Tgstation.Server.Api/Models/Job.cs | 35 ++--------- .../Models/DreamDaemonSettings.cs | 16 +++++ src/Tgstation.Server.Host/Models/Instance.cs | 3 + src/Tgstation.Server.Host/Models/Job.cs | 10 +++ 7 files changed, 138 insertions(+), 80 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/Job.cs create mode 100644 src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs create mode 100644 src/Tgstation.Server.Host/Models/Job.cs diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 6039fb4f34..b6fff22b40 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models @@ -8,66 +9,21 @@ namespace Tgstation.Server.Api.Models /// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server /// [Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)] - public sealed class DreamDaemon + public sealed class DreamDaemon : DreamDaemonSettings { - /// - /// When the live revision was compiled - /// - [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] - public CompileJob CompileJob { get; set; } - - /// - /// If starts when it's starts - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] - public bool? AutoStart { get; set; } - /// /// The current status of /// [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] public DreamDaemonStatus? Status { get; set; } - /// - /// If the BYOND web client can be used to connect to the game server - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] - public bool? AllowWebClient { get; set; } - - /// - /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)] - public bool? SoftRestart { get; set; } - - /// - /// If the server is undergoing a soft shutdown - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] - public bool? SoftShutdown { get; set; } - - /// - /// The level of - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] - public DreamDaemonSecurity? SecurirtyLevel { get; set; } - - /// - /// The first port uses. This should be the publically advertised port - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort? PrimaryPort { get; set; } - - /// - /// The second port uses - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort? SecondaryPort { get; set; } + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] + public DreamDaemonSecurity? CurrentSecurityLevel { get; set; } /// /// The port the running instance is set to /// [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] - public bool CurrentPort { get; set; } + public ushort? CurrentPort { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs new file mode 100644 index 0000000000..cdedd60a93 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -0,0 +1,63 @@ +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + public class DreamDaemonSettings + { + /// + /// When the live revision was compiled + /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] + public CompileJob CompileJob { get; set; } + + /// + /// If starts when it's starts + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] + [Required] + public bool? AutoStart { get; set; } + + /// + /// If the BYOND web client can be used to connect to the game server + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] + [Required] + public bool? AllowWebClient { get; set; } + + /// + /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)] + [Required] + public bool? SoftRestart { get; set; } + + /// + /// If the server is undergoing a soft shutdown + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] + [Required] + public bool? SoftShutdown { get; set; } + + /// + /// The level of + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] + [Required] + public DreamDaemonSecurity? SecurityLevel { get; set; } + + /// + /// The first port uses. This should be the publically advertised port + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] + [Required] + public ushort? PrimaryPort { get; set; } + + /// + /// The second port uses + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] + [Required] + public ushort? SecondaryPort { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs new file mode 100644 index 0000000000..387cfac884 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models.Internal +{ + public class Job + { + /// + /// The ID + /// + public long Id { get; set; } + + /// + /// Human readable description of the + /// + [Required] + public string Description { get; set; } + + /// + /// When the was started + /// + [Required] + public DateTimeOffset StartedAt { get; set; } + + public User StartedBy { get; set; } + + /// + /// When the stopped + /// + public DateTimeOffset StoppedAt { get; set; } + + /// + /// If the was cancelled + /// + public bool Cancelled { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs index d0231963b4..e5c23eaa40 100644 --- a/src/Tgstation.Server.Api/Models/Job.cs +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -1,46 +1,19 @@ -using System; - -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// /// Represents a long running job on the server. Model is read-only, updates attempt to cancel the job /// [Model] - public sealed class Job + public sealed class Job : Internal.Job { - /// - /// The ID - /// - public long Id { get; set; } - - /// - /// Human readable description of the - /// - public string Description { get; set; } - - /// - /// When the was started - /// - public DateTimeOffset StartedAt { get; set; } - - /// - /// When the stopped - /// - public DateTimeOffset StoppedAt { get; set; } - - /// - /// If the was cancelled - /// - public bool Cancelled { get; set; } - /// /// If the has incremental progress, this will range from 1 - 100. 0 otherwise /// public int Progress { get; set; } /// - /// If the current user has permission to cancel the job. Will be if isn't + /// If the current user has permission to cancel the job /// - public bool? UserCanCancel { get; set; } + public bool UserCanCancel { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs new file mode 100644 index 0000000000..9daccbbde5 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + sealed class DreamDaemonSettings : Api.Models.Internal.DreamDaemonSettings + { + long Id { get; set; } + + int? ProcessId { get; set; } + + long InstanceId { get; set; } + + [Required] + Instance Instance { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 174bf3d36f..859ceac3c2 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -21,6 +21,9 @@ namespace Tgstation.Server.Host.Models [Required] DreamMakerSettings DreamMakerSettings { get; set; } + [Required] + DreamDaemonSettings DreamDaemonSettings { get; set; } + /// /// The s in the /// diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs new file mode 100644 index 0000000000..29c978ce2c --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + sealed class Job : Api.Models.Internal.Job + { + [Required] + new public User StartedBy { get; set; } + } +} From 4b760fadcb6fcf4352bede7be9aa918fa07dc89a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 7 Apr 2018 13:22:53 -0400 Subject: [PATCH 070/397] Hopefully done messing with models for now --- .../Models/Administration.cs | 22 ++-------------- .../Models/Internal/ServerSettings.cs | 25 +++++++++++++++++++ .../Models/IDatabaseContext.cs | 21 ++++++++++++++++ src/Tgstation.Server.Host/Models/Job.cs | 2 ++ .../Models/ServerSettings.cs | 7 ++++++ 5 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs create mode 100644 src/Tgstation.Server.Host/Models/ServerSettings.cs diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 7fd8bdfc84..1aed2dad16 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -8,32 +8,14 @@ namespace Tgstation.Server.Api.Models /// Metadata about an installation /// [Model(RightsType.Administration)] - public sealed class Administration + public sealed class Administration : Internal.ServerSettings { - /// - /// Use the specified Windows/UNIX authentication to authorize users. Setting this to enables full administrative anonymous access - /// - [Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)] - public string SystemAuthenticationGroup { get; set; } - - /// - /// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information. - /// - [Permissions(ReadRight = AdministrationRights.ChangeTelemetry, WriteRight = AdministrationRights.ChangeTelemetry)] - public bool EnableTelemetry { get; set; } - /// /// If the instances will not be stopped when the server exits. Resets to when the server restarts /// [Permissions(ReadRight = AdministrationRights.SoftStop, WriteRight = AdministrationRights.SoftStop)] public bool SoftStop { get; set; } - - /// - /// The git repository to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to upstream pulls will be disabled entirely - /// - [Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)] - public string UpstreamRepository { get; set; } - + /// /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is higher than 's the update cannot be applied due to API changes /// diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs new file mode 100644 index 0000000000..7c30871cea --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs @@ -0,0 +1,25 @@ +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + public class ServerSettings + { + /// + /// Use the specified Windows/UNIX authentication to authorize users. Setting this to enables full administrative anonymous access + /// + [Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)] + public string SystemAuthenticationGroup { get; set; } + + /// + /// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information. + /// + [Permissions(ReadRight = AdministrationRights.ChangeTelemetry, WriteRight = AdministrationRights.ChangeTelemetry)] + public bool EnableTelemetry { get; set; } + + /// + /// The git repository to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to upstream pulls will be disabled entirely + /// + [Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)] + public string UpstreamRepository { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 7b44a65ca6..9cbf54d537 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -9,12 +9,33 @@ namespace Tgstation.Server.Host.Models /// interface IDatabaseContext { + /// + /// The in the + /// + ServerSettings ServerSettings { get; } + + /// + /// The s in the + /// DbSet Users { get; } + /// + /// The s in the + /// DbSet Instances { get; } + /// + /// Saves changes made to the + /// + /// The for the operation + /// A representing the running operation Task Save(CancellationToken cancellationToken); + /// + /// Creates and migrates the + /// + /// The for the operation + /// A representing the running operation Task Initialize(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 29c978ce2c..2becf695ff 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -6,5 +6,7 @@ namespace Tgstation.Server.Host.Models { [Required] new public User StartedBy { get; set; } + + Instance Instance { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/ServerSettings.cs b/src/Tgstation.Server.Host/Models/ServerSettings.cs new file mode 100644 index 0000000000..60576514ec --- /dev/null +++ b/src/Tgstation.Server.Host/Models/ServerSettings.cs @@ -0,0 +1,7 @@ +namespace Tgstation.Server.Host.Models +{ + sealed class ServerSettings : Api.Models.Internal.ServerSettings + { + long Id { get; set; } + } +} From 8c470440ec5b54df06183b77ff48e0ac6f2f5768 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 7 Apr 2018 13:42:26 -0400 Subject: [PATCH 071/397] IServerUpdateConsumer --- .../Core/IServerUpdateConsumer.cs | 7 +++++ src/Tgstation.Server.Host/Server.cs | 30 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs diff --git a/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs b/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs new file mode 100644 index 0000000000..b22df996ed --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs @@ -0,0 +1,7 @@ +namespace Tgstation.Server.Host.Core +{ + interface IServerUpdateConsumer + { + void ApplyUpdate(string updatePath); + } +} diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 5de89070ff..506424c49c 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; using System; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -9,16 +10,21 @@ using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host { /// - sealed class Server : IServer + sealed class Server : IServer, IServerUpdateConsumer { /// - public string UpdatePath => null; + public string UpdatePath { get; private set; } /// /// The for the /// readonly IWebHostBuilder webHostBuilder; + /// + /// The for the + /// + CancellationTokenSource cancellationTokenSource; + /// /// Construct a /// @@ -29,8 +35,26 @@ namespace Tgstation.Server.Host [ExcludeFromCodeCoverage] public async Task RunAsync(CancellationToken cancellationToken) { - using (var webHost = webHostBuilder.UseStartup().Build()) + cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + using (cancellationTokenSource) + using (var webHost = webHostBuilder + .UseStartup() + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .Build() + ) await webHost.RunAsync(cancellationToken).ConfigureAwait(false); } + + /// + public void ApplyUpdate(string updatePath) + { + lock (this) + { + if (updatePath != null) + throw new InvalidOperationException("ApplyUpdate has already been called!"); + UpdatePath = updatePath; + } + cancellationTokenSource.Cancel(); + } } } From c63549fa93213815b2444febe421dca1d04afd52 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 7 Apr 2018 14:42:32 -0400 Subject: [PATCH 072/397] More database context stuff --- build/Dockerfile | 6 ++ .../Properties/launchSettings.json | 20 ++++ .../Configuration/DatabaseConfiguration.cs | 8 ++ .../Configuration/DatabaseType.cs | 9 ++ src/Tgstation.Server.Host/Core/Application.cs | 13 ++- .../Models/DatabaseContext.cs | 97 +++++++++++++++++++ .../Models/IDatabaseContext.cs | 12 ++- src/Tgstation.Server.Host/Models/Instance.cs | 5 + .../Tgstation.Server.Host.csproj | 18 ++++ .../appsettings.Development.json | 24 +++++ .../appsettings.Docker.json | 6 ++ src/Tgstation.Server.Host/appsettings.json | 24 +++++ 12 files changed, 235 insertions(+), 7 deletions(-) create mode 100644 src/Tgstation.Server.Host.Console/Properties/launchSettings.json create mode 100644 src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs create mode 100644 src/Tgstation.Server.Host/Configuration/DatabaseType.cs create mode 100644 src/Tgstation.Server.Host/Models/DatabaseContext.cs create mode 100644 src/Tgstation.Server.Host/appsettings.Development.json create mode 100644 src/Tgstation.Server.Host/appsettings.Docker.json create mode 100644 src/Tgstation.Server.Host/appsettings.json diff --git a/build/Dockerfile b/build/Dockerfile index 5332fa7e91..b0f90f3cad 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -38,6 +38,12 @@ RUN dotnet publish -c Release -o /app FROM microsoft/dotnet:2.1-runtime EXPOSE 80 + WORKDIR /app + COPY --from=publish /app . + +RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json +VOLUME ["/config_data"] + ENTRYPOINT ["dotnet", "Tgstation.Server.Host.Console.dll"] diff --git a/src/Tgstation.Server.Host.Console/Properties/launchSettings.json b/src/Tgstation.Server.Host.Console/Properties/launchSettings.json new file mode 100644 index 0000000000..df4509a62a --- /dev/null +++ b/src/Tgstation.Server.Host.Console/Properties/launchSettings.json @@ -0,0 +1,20 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:51882/", + "sslPort": 0 + } + }, + "profiles": { + "Tgstation.Server.Host.Console": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:51885/" + } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs new file mode 100644 index 0000000000..b356408c50 --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -0,0 +1,8 @@ +namespace Tgstation.Server.Host.Configuration +{ + sealed class DatabaseConfiguration + { + public DatabaseType DatabaseType { get; set; } + public string ConnectionString { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs new file mode 100644 index 0000000000..919c82ed47 --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs @@ -0,0 +1,9 @@ +namespace Tgstation.Server.Host.Configuration +{ + enum DatabaseType + { + SqlServer, + MySql, + Sqlite + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 1c4cf52f9c..e568a1526f 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Builder; +using Cyberboss.AspNetCore.AsyncInitializer; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Globalization; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { @@ -35,10 +37,15 @@ namespace Tgstation.Server.Host.Core if (services == null) throw new ArgumentNullException(nameof(services)); + services.Configure(configuration.GetSection("Database")); + services.AddMvc(); services.AddOptions(); services.AddLocalization(); - } + + services.AddDbContext(); + services.AddScoped(x => x.GetRequiredService()); + } /// /// Configure the @@ -70,6 +77,8 @@ namespace Tgstation.Server.Host.Core SupportedUICultures = supportedCultures, }); + applicationBuilder.UseAsyncInitialization((databaseContext, cancellationToken) => databaseContext.Initialize(cancellationToken)); + applicationBuilder.UseSystemAuthentication(); applicationBuilder.UseMvc(); diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs new file mode 100644 index 0000000000..6eaabd13b4 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -0,0 +1,97 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Configuration; +using ZNetCS.AspNetCore.Logging.EntityFrameworkCore; + +namespace Tgstation.Server.Host.Models +{ + sealed class DatabaseContext : DbContext, IDatabaseContext + { + public DbSet ServerSettings { get; set; } + + public DbSet Users { get; set; } + + public DbSet Instances { get; set; } + + /// + /// The for s + /// + public DbSet Logs { get; set; } + + /// + /// The for the + /// + readonly DatabaseConfiguration databaseConfiguration; + /// + /// The for the + /// + readonly ILoggerFactory loggerFactory; + /// + /// The for the + /// + readonly IHostingEnvironment hostingEnvironment; + + /// + /// Construct a + /// + /// The for the + /// The containing the value of + /// The value of + /// The value of + public DatabaseContext(DbContextOptions options, IOptions databaseConfigurationOptions, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment) : base(options) + { + databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // build default model. + LogModelBuilderHelper.Build(modelBuilder.Entity()); + // real relation database can map table: + modelBuilder.Entity().ToTable(nameof(Log)); + } + + /// + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + switch (databaseConfiguration.DatabaseType) + { + case DatabaseType.MySql: + optionsBuilder.UseMySQL(databaseConfiguration.ConnectionString); + break; + case DatabaseType.Sqlite: + optionsBuilder.UseSqlite(databaseConfiguration.ConnectionString); + break; + case DatabaseType.SqlServer: + optionsBuilder.UseSqlServer(databaseConfiguration.ConnectionString); + break; + } + optionsBuilder.UseLoggerFactory(loggerFactory); + if (hostingEnvironment.IsDevelopment()) + optionsBuilder.EnableSensitiveDataLogging(); + } + + /// + public Task GetServerSettings(CancellationToken cancellationToken) => ServerSettings.FirstOrDefaultAsync(cancellationToken); + + /// + public async Task Initialize(CancellationToken cancellationToken) + { + await Database.EnsureCreatedAsync(cancellationToken).ConfigureAwait(false); + await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 9cbf54d537..9330d3d175 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -9,11 +9,6 @@ namespace Tgstation.Server.Host.Models /// interface IDatabaseContext { - /// - /// The in the - /// - ServerSettings ServerSettings { get; } - /// /// The s in the /// @@ -24,6 +19,13 @@ namespace Tgstation.Server.Host.Models /// DbSet Instances { get; } + /// + /// Get the in the + /// + /// The for the operation + /// A resulting in the in the + Task GetServerSettings(CancellationToken cancellationToken); + /// /// Saves changes made to the /// diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 859ceac3c2..6ebb7f8507 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -38,5 +38,10 @@ namespace Tgstation.Server.Host.Models /// The s in the /// public List CompileJobs { get; set; } + + /// + /// The in the + /// + public List Jobs { get; set; } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 40ab8fa5f7..bc0d5b6b3f 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -17,12 +17,30 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Tgstation.Server.Host/appsettings.Development.json b/src/Tgstation.Server.Host/appsettings.Development.json new file mode 100644 index 0000000000..d3796e9ae8 --- /dev/null +++ b/src/Tgstation.Server.Host/appsettings.Development.json @@ -0,0 +1,24 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Debug" + } + }, + "Console": { + "LogLevel": { + "Default": "Trace" + } + }, + "EntityFramework": { + "LogLevel": { + "Default": "Warning" + } + } + }, + "Database": { + "DatabaseType": "Sqlite", + "ConnectionString": "Data Source=TestDB.sqlite3;Version=3;" + } +} diff --git a/src/Tgstation.Server.Host/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json new file mode 100644 index 0000000000..906e461ee4 --- /dev/null +++ b/src/Tgstation.Server.Host/appsettings.Docker.json @@ -0,0 +1,6 @@ +{ + "Database": { + "DatabaseType": "SqlServer", + "ConnectionString": "" + } +} diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json new file mode 100644 index 0000000000..bfcbeffa32 --- /dev/null +++ b/src/Tgstation.Server.Host/appsettings.json @@ -0,0 +1,24 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Debug" + } + }, + "Console": { + "LogLevel": { + "Default": "Trace" + } + }, + "EntityFramework": { + "LogLevel": { + "Default": "Warning" + } + } + }, + "Database": { + "DatabaseType": "Sqlite", + "ConnectionString": "Fake" + } +} From 399186874e7dd008f854a2baa73fb2a1ec4bccb6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 8 Apr 2018 13:35:04 -0400 Subject: [PATCH 073/397] We're somewhere --- .../Models/ChatChannel.cs | 23 ++++++++ .../Models/ChatSettings.cs | 22 +++---- src/Tgstation.Server.Api/Models/CompileJob.cs | 11 +--- .../Models/Configuration.cs | 2 - src/Tgstation.Server.Api/Models/Instance.cs | 1 + .../Models/InstanceUser.cs | 10 +--- .../Models/Internal/DreamDaemonSettings.cs | 21 +++---- .../Models/Internal/Job.cs | 1 + .../Models/Internal/RepositorySettings.cs | 56 ++++++++++++++++++ .../Models/Internal/ServerSettings.cs | 2 +- .../Models/Internal/Token.cs | 47 +++++++++++++++ src/Tgstation.Server.Api/Models/Repository.cs | 53 +---------------- .../Models/RevisionInformation.cs | 15 ++--- src/Tgstation.Server.Api/Models/TestMerge.cs | 6 -- src/Tgstation.Server.Api/Models/Token.cs | 58 +++++-------------- src/Tgstation.Server.Api/Models/User.cs | 18 +++++- .../Rights/AdministrationRights.cs | 5 +- .../Tgstation.Server.Api.csproj | 4 +- .../Properties/launchSettings.json | 1 + src/Tgstation.Server.Host/Core/Application.cs | 15 +++-- .../Core/AuthenticationContext.cs | 2 +- .../Core/IAuthenticationContext.cs | 2 +- .../Models/ChatSettings.cs | 4 +- .../Models/CompileJob.cs | 10 ---- .../Models/DatabaseContext.cs | 26 +++++++-- .../Models/DbCompileJob.cs | 13 +++++ src/Tgstation.Server.Host/Models/DbUser.cs | 25 ++++++++ .../Models/DreamDaemonSettings.cs | 14 +++-- .../Models/DreamMakerSettings.cs | 10 +++- .../Models/IDatabaseContext.cs | 4 +- src/Tgstation.Server.Host/Models/Instance.cs | 13 +++-- .../Models/InstanceUser.cs | 7 +++ src/Tgstation.Server.Host/Models/Job.cs | 3 +- .../Models/RepositorySettings.cs | 16 +++++ .../Models/RevisionInformation.cs | 11 ++++ .../Models/ServerSettings.cs | 6 +- src/Tgstation.Server.Host/Models/TestMerge.cs | 2 +- src/Tgstation.Server.Host/Models/User.cs | 21 ------- .../Tgstation.Server.Host.csproj | 14 +++-- .../appsettings.Development.json | 2 +- 40 files changed, 345 insertions(+), 231 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/ChatChannel.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/Token.cs delete mode 100644 src/Tgstation.Server.Host/Models/CompileJob.cs create mode 100644 src/Tgstation.Server.Host/Models/DbCompileJob.cs create mode 100644 src/Tgstation.Server.Host/Models/DbUser.cs create mode 100644 src/Tgstation.Server.Host/Models/InstanceUser.cs create mode 100644 src/Tgstation.Server.Host/Models/RepositorySettings.cs create mode 100644 src/Tgstation.Server.Host/Models/RevisionInformation.cs delete mode 100644 src/Tgstation.Server.Host/Models/User.cs diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs new file mode 100644 index 0000000000..737b41203d --- /dev/null +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -0,0 +1,23 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// Indicates a chat channel + /// + public sealed class ChatChannel + { + /// + /// The column ID + /// + public long Id { get; set; } + + /// + /// The IRC channel name + /// + public string IrcChannel { get; set; } + + /// + /// The Discord channel ID + /// + public long DiscordChannelId { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/ChatSettings.cs b/src/Tgstation.Server.Api/Models/ChatSettings.cs index a528c1d773..593e6c4a8c 100644 --- a/src/Tgstation.Server.Api/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/ChatSettings.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models @@ -19,6 +21,7 @@ namespace Tgstation.Server.Api.Models /// The IRC server name /// [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] + [Required] public string IrcHost { get; set; } /// @@ -33,18 +36,6 @@ namespace Tgstation.Server.Api.Models [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] public string IrcNickServPassword { get; set; } - /// - /// Channels the IRC client should join/listen/announce in and allow admin commands - /// - [Permissions(WriteRight = ChatSettingsRights.SetIrcChannels)] - public List IrcAdminChannels { get; set; } - - /// - /// Channels the IRC client should join/listen/announce in - /// - [Permissions(WriteRight = ChatSettingsRights.SetIrcChannels)] - public List IrcGeneralChannels { get; set; } - /// /// If the Discord bot is enabled /// @@ -58,14 +49,15 @@ namespace Tgstation.Server.Api.Models public string DiscordBotToken { get; set; } /// - /// Channels the Discord bot should listen/announce in and allow admin commands + /// Channels the bot should listen/announce in and allow admin commands /// [Permissions(WriteRight = ChatSettingsRights.SetDiscordChannels)] - public List DiscordAdminChannels { get; set; } + public virtual List AdminChannels { get; set; } + /// /// Channels the Discord bot should listen/announce in /// [Permissions(WriteRight = ChatSettingsRights.SetDiscordChannels)] - public List DiscordGeneralChannels { get; set; } + public virtual List GeneralChannels { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 1133d86e50..4f1592a25e 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -1,5 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + namespace Tgstation.Server.Api.Models { public class CompileJob @@ -39,17 +41,10 @@ namespace Tgstation.Server.Api.Models /// Exit code of DM. If , the job was cancelled /// public int? ExitCode { get; set; } - + /// /// Git revision the compiler ran on. Not modifiable /// - [Required] public RevisionInformation RevisionInformation { get; set; } - - /// - /// Git revision of the origin branch the compiler ran on. Not modifiable - /// - [Required] - public string OriginRevision { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Configuration.cs b/src/Tgstation.Server.Api/Models/Configuration.cs index 4b750c9e6a..16b57be176 100644 --- a/src/Tgstation.Server.Api/Models/Configuration.cs +++ b/src/Tgstation.Server.Api/Models/Configuration.cs @@ -30,9 +30,7 @@ namespace Tgstation.Server.Api.Models /// /// The content of the file. Will be if is or during listing operations /// -#pragma warning disable CA1819 // Properties should not return arrays public byte[] Content { get; set; } -#pragma warning restore CA1819 // Properties should not return arrays /// /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 717efc7505..c1f198d30a 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -26,6 +26,7 @@ namespace Tgstation.Server.Api.Models /// The path to where the is located /// [Permissions(WriteRight = InstanceManagerRights.Relocate)] + [Required] public string Path { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 0dd9c3ca9e..2173fc2681 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -6,14 +6,8 @@ namespace Tgstation.Server.Api.Models /// Represents a s permissions in an /// [Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)] - public sealed class InstanceUser + public class InstanceUser { - /// - /// See definition in - /// - [Permissions(DenyWrite = true)] - public long Id { get; set; } - /// /// The of the /// @@ -37,7 +31,7 @@ namespace Tgstation.Server.Api.Models /// /// The of the /// - public ChatSettingsRights ChatRights { get; set; } + public ChatSettingsRights ChatSettingsRights { get; set; } /// /// The of the diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index cdedd60a93..6288508a43 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -15,49 +15,42 @@ namespace Tgstation.Server.Api.Models.Internal /// If starts when it's starts /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] - [Required] - public bool? AutoStart { get; set; } + public bool AutoStart { get; set; } /// /// If the BYOND web client can be used to connect to the game server /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] - [Required] - public bool? AllowWebClient { get; set; } + public bool AllowWebClient { get; set; } /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)] - [Required] - public bool? SoftRestart { get; set; } + public bool SoftRestart { get; set; } /// /// If the server is undergoing a soft shutdown /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] - [Required] - public bool? SoftShutdown { get; set; } + public bool SoftShutdown { get; set; } /// /// The level of /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] - [Required] - public DreamDaemonSecurity? SecurityLevel { get; set; } + public DreamDaemonSecurity SecurityLevel { get; set; } /// /// The first port uses. This should be the publically advertised port /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - [Required] - public ushort? PrimaryPort { get; set; } + public ushort PrimaryPort { get; set; } /// /// The second port uses /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - [Required] - public ushort? SecondaryPort { get; set; } + public ushort SecondaryPort { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index 387cfac884..26266a20c5 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -22,6 +22,7 @@ namespace Tgstation.Server.Api.Models.Internal [Required] public DateTimeOffset StartedAt { get; set; } + [Required] public User StartedBy { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs new file mode 100644 index 0000000000..426ad618d2 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -0,0 +1,56 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + public class RepositorySettings + { + /// + /// The origin URL. If , the does not exist + /// + [Permissions(WriteRight = RepositoryRights.SetOrigin)] + public string Origin { get; set; } + + [Permissions(DenyWrite = true)] + public RevisionInformation RevisionInformation { get; set; } + + /// + /// The name of the committer + /// + [Permissions(WriteRight = RepositoryRights.ChangeCommitter)] + [Required] + public string CommitterName { get; set; } + + /// + /// The e-mail of the committer + /// + [Permissions(WriteRight = RepositoryRights.ChangeCommitter)] + [Required] + public string CommitterEmail { get; set; } + + /// + /// The username to access the git repository with + /// + [Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)] + public string AccessUser { get; set; } + + /// + /// The token/password to access the git repository with + /// + [Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)] + public string AccessToken { get; set; } + + /// + /// If commits created from testmerges are pushed to the remote + /// + [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] + public bool PushTestMergeCommits { get; set; } + + /// + /// How often the automatically updates in minutes + /// + [Permissions(WriteRight = RepositoryRights.ChangeAutoUpdate)] + public int? AutoUpdateInterval { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs index 7c30871cea..7aa96da12b 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Api.Models.Internal public class ServerSettings { /// - /// Use the specified Windows/UNIX authentication to authorize users. Setting this to enables full administrative anonymous access + /// Use the specified Windows/POSIX authentication group to authorize users. Changing this may enable or disable s depending on how they were configured. Setting this to changes the authentication mode to database. /// [Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)] public string SystemAuthenticationGroup { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Internal/Token.cs b/src/Tgstation.Server.Api/Models/Internal/Token.cs new file mode 100644 index 0000000000..3b1cad7574 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/Token.cs @@ -0,0 +1,47 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + /// + /// Represents an access token for the server. Read action generates a new one. Update action expires all for user + /// + [Model(RightsType.Token, CanList = true)] + public class Token + { + /// + /// The id of the . Not modifiable + /// + public long Id { get; set; } + + /// + /// The user agent that created the + /// + [Required] + public string ClientUserAgent { get; set; } + + [Required, MinLength(4), MaxLength(16)] + public byte[] IssuedTo { get; set; } + + [Required, MinLength(4), MaxLength(16)] + public byte[] LastUsedBy { get; set; } + + /// + /// When the was originally issued + /// + [Required] + public DateTimeOffset IssuedAt { get; set; } + + /// + /// When the was last used + /// + public DateTimeOffset LastUsedAt { get; set; } + + /// + /// The token . Not modifiable, only appears once + /// + [Required] + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index eca73cace8..81e796ef00 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -7,17 +7,8 @@ namespace Tgstation.Server.Api.Models /// Represents a git repository /// [Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)] - public sealed class Repository + public sealed class Repository : Internal.RepositorySettings { - /// - /// The origin URL. If , the does not exist - /// - [Permissions(WriteRight = RepositoryRights.SetOrigin)] - public string Origin { get; set; } - - [Permissions(DenyWrite = true)] - public RevisionInformation RevisionInformation { get; set; } - /// /// The commit HEAD points to /// @@ -30,49 +21,7 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = RepositoryRights.SetReference)] public string Reference { get; set; } - /// - /// The name of the committer - /// - [Permissions(WriteRight = RepositoryRights.ChangeCommitter)] - public string CommitterName { get; set; } - - /// - /// The e-mail of the committer - /// - [Permissions(WriteRight = RepositoryRights.ChangeCommitter)] - public string CommitterEmail { get; set; } - - /// - /// List of s in the repository - /// - [Permissions(DenyWrite = true)] - public List TestMerges { get; set; } - [Permissions(WriteRight = RepositoryRights.MergePullRequest)] public List NewTestMerges { get; set; } - - /// - /// The username to access the git repository with - /// - [Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)] - public string AccessUser { get; set; } - - /// - /// The token/password to access the git repository with - /// - [Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)] - public string AccessToken { get; set; } - - /// - /// If commits created from testmerges are pushed to the remote - /// - [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] - public bool PushTestMergeCommits { get; set; } - - /// - /// How often the automatically updates in minutes - /// - [Permissions(WriteRight = RepositoryRights.ChangeAutoUpdate)] - public int? AutoUpdateInterval { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs index e3724896c6..ea7165b24b 100644 --- a/src/Tgstation.Server.Api/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs @@ -1,16 +1,17 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace Tgstation.Server.Api.Models { - public sealed class RevisionInformation + public class RevisionInformation { - [Key] - string Revision { get; set; } + [Required, StringLength(40)] + public string Revision { get; set; } - [Required] - string OriginRevision { get; set; } - - List TestMerges { get; set; } + [Required, StringLength(40)] + public string OriginRevision { get; set; } + + public List TestMerges { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/TestMerge.cs b/src/Tgstation.Server.Api/Models/TestMerge.cs index 93724a46fa..1cb64db9ea 100644 --- a/src/Tgstation.Server.Api/Models/TestMerge.cs +++ b/src/Tgstation.Server.Api/Models/TestMerge.cs @@ -7,14 +7,8 @@ namespace Tgstation.Server.Api.Models { public long Id { get; set; } - [Required] - RevisionInformation RevisionInformation { get; set; } - [Required] public DateTimeOffset MergedAt { get; set; } - - [Required] - public DateTimeOffset RemovedAt { get; set; } public User MergedBy { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index dffa764376..b709bea3d8 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,48 +1,22 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Net; -using Tgstation.Server.Api.Rights; +using System.Net; namespace Tgstation.Server.Api.Models { - /// - /// Represents an access token for the server. Read action generates a new one. Update action expires all for user - /// - [Model(RightsType.Token, CanList = true)] - public sealed class Token + /// + public sealed class Token : Internal.Token { - /// - /// The id of the . Not modifiable - /// - public long Id { get; } + /// + new public IPAddress IssuedTo + { + get => new IPAddress(base.IssuedTo); + set => base.IssuedTo = value.GetAddressBytes(); + } - /// - /// The user agent that created the - /// - [Required] - public string ClientUserAgent { get; set; } - - /// - /// The the was originally issued to - /// - [Required] - public IPAddress IssuedTo { get; set; } - - /// - /// When the was originally issued - /// - [Required] - public DateTimeOffset IssuedAt { get; set; } - - /// - /// When the was last used - /// - public DateTimeOffset LastUsedAt { get; set; } - - /// - /// The token . Not modifiable, only appears once - /// - [Required] - public string Value { get; set; } + /// + new public IPAddress LastUsedBy + { + get => new IPAddress(base.LastUsedBy); + set => base.LastUsedBy = value.GetAddressBytes(); + } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index e53f42e419..70bd28bed1 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -1,4 +1,6 @@ -using Tgstation.Server.Api.Rights; +using System; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { @@ -14,26 +16,36 @@ namespace Tgstation.Server.Api.Models [Permissions(DenyWrite = true)] public long Id { get; set; } + [Permissions(DenyWrite = true)] + [Required] + public DateTimeOffset CreatedAt { get; set; } + /// /// The SID/UID of the on Windows/POSIX respectively /// [Permissions(DenyWrite = true)] - public string SystemId { get; set; } + public string SystemIdentifier { get; set; } + + [Permissions(DenyWrite = true)] + public bool Enabled { get; set; } /// /// The name of the /// - [Permissions(DenyWrite = true)] + [Permissions(WriteRight = AdministrationRights.EditUsers)] + [Required] public string Name { get; set; } /// /// The for the /// + [Permissions(WriteRight = AdministrationRights.EditUsers)] public AdministrationRights AdministrationRights { get; set; } /// /// The for the /// + [Permissions(WriteRight = AdministrationRights.EditUsers)] public InstanceManagerRights InstanceManagerRights { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index 5a9a37aaf8..c27ae958c9 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Api.Rights /// None = 0, /// - /// User can change + /// User can change /// ChangeAuthenticationGroup = 1, /// @@ -35,7 +35,6 @@ namespace Tgstation.Server.Api.Rights /// /// User can change /// - SetUpstreamRepository - + SetUpstreamRepository = 32 } } diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 2e3f40a448..51641091d2 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -10,12 +10,12 @@ true bin\Release\netstandard2.0\Tgstation.Server.Api.xml - 1701;1702;1705;CA2227 + 1701;1702;1705;CA2227;CA1819 latest - 1701;1702;1705;CA2227 + 1701;1702;1705;CA2227;CA1819 diff --git a/src/Tgstation.Server.Host.Console/Properties/launchSettings.json b/src/Tgstation.Server.Host.Console/Properties/launchSettings.json index df4509a62a..5991274947 100644 --- a/src/Tgstation.Server.Host.Console/Properties/launchSettings.json +++ b/src/Tgstation.Server.Host.Console/Properties/launchSettings.json @@ -10,6 +10,7 @@ "profiles": { "Tgstation.Server.Host.Console": { "commandName": "Project", + "workingDirectory": "bin\\Debug\\netcoreapp2.0", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index e568a1526f..2a8d4f6f78 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Globalization; +using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core @@ -36,8 +37,8 @@ namespace Tgstation.Server.Host.Core { if (services == null) throw new ArgumentNullException(nameof(services)); - - services.Configure(configuration.GetSection("Database")); + var workingDir = Environment.CurrentDirectory; + services.Configure(configuration.GetSection("Database")); services.AddMvc(); services.AddOptions(); @@ -58,7 +59,7 @@ namespace Tgstation.Server.Host.Core throw new ArgumentNullException(nameof(applicationBuilder)); if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment)); - + if (hostingEnvironment.IsDevelopment()) applicationBuilder.UseDeveloperExceptionPage(); @@ -77,9 +78,13 @@ namespace Tgstation.Server.Host.Core SupportedUICultures = supportedCultures, }); - applicationBuilder.UseAsyncInitialization((databaseContext, cancellationToken) => databaseContext.Initialize(cancellationToken)); + applicationBuilder.UseAsyncInitialization(async (cancellationToken) => + { + using (var scop = applicationBuilder.ApplicationServices.CreateScope()) + await scop.ServiceProvider.GetRequiredService().Initialize(cancellationToken).ConfigureAwait(false); + }); - applicationBuilder.UseSystemAuthentication(); + applicationBuilder.UseSystemAuthentication(); applicationBuilder.UseMvc(); } diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs index aa8235f1b8..fe4c5f9b8d 100644 --- a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs @@ -99,7 +99,7 @@ namespace Tgstation.Server.Host.Core public void Dispose() => SystemIdentity.Dispose(); /// - public Task User(CancellationToken cancellationToken) + public Task User(CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs index 1d2466bbaf..a198c93ceb 100644 --- a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Core /// /// The for the operation /// A resulting in the represented by the - Task User(CancellationToken cancellationToken); + Task User(CancellationToken cancellationToken); /// /// The represented by and a given diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs index 4c7ed23a76..0b798988c0 100644 --- a/src/Tgstation.Server.Host/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs deleted file mode 100644 index 5d1b7b93cb..0000000000 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Tgstation.Server.Host.Models -{ - sealed class CompileJob : Api.Models.CompileJob - { - [Required] - new User TriggeredBy { get; set; } - } -} diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 6eaabd13b4..677733bc83 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using System; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; using ZNetCS.AspNetCore.Logging.EntityFrameworkCore; @@ -12,16 +13,30 @@ namespace Tgstation.Server.Host.Models { sealed class DatabaseContext : DbContext, IDatabaseContext { + /// public DbSet ServerSettings { get; set; } - public DbSet Users { get; set; } + /// + public DbSet Users { get; set; } + /// public DbSet Instances { get; set; } /// /// The for s /// public DbSet Logs { get; set; } + public DbSet InstanceUsers { get; set; } + public DbSet ChatChannels { get; set; } + public DbSet ChatSettings { get; set; } + public DbSet DreamDaemonSettings { get; set; } + public DbSet DreamMakerSettings { get; set; } + public DbSet CompileJobs { get; set; } + public DbSet Jobs { get; set; } + public DbSet Tokens { get; set; } + public DbSet TestMerges { get; set; } + public DbSet RevisionInformations { get; set; } + public DbSet RepositorySettings { get; set; } /// /// The for the @@ -39,11 +54,11 @@ namespace Tgstation.Server.Host.Models /// /// Construct a /// - /// The for the + /// The for the /// The containing the value of /// The value of /// The value of - public DatabaseContext(DbContextOptions options, IOptions databaseConfigurationOptions, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment) : base(options) + public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment) : base(dbContextOptions) { databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); @@ -57,8 +72,9 @@ namespace Tgstation.Server.Host.Models // build default model. LogModelBuilderHelper.Build(modelBuilder.Entity()); - // real relation database can map table: - modelBuilder.Entity().ToTable(nameof(Log)); + modelBuilder.Entity().ToTable(nameof(Logs)); + + modelBuilder.Entity().HasIndex(x => x.Revision).IsUnique(); } /// diff --git a/src/Tgstation.Server.Host/Models/DbCompileJob.cs b/src/Tgstation.Server.Host/Models/DbCompileJob.cs new file mode 100644 index 0000000000..6392c06024 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/DbCompileJob.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + sealed class DbCompileJob : Api.Models.CompileJob + { + [Required] + new public DbUser TriggeredBy { get; set; } + + [Required] + new public RevisionInformation RevisionInformation { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/DbUser.cs b/src/Tgstation.Server.Host/Models/DbUser.cs new file mode 100644 index 0000000000..401ce517be --- /dev/null +++ b/src/Tgstation.Server.Host/Models/DbUser.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represents a in the database + /// + sealed class DbUser : Api.Models.User + { + public string PasswordHash { get; set; } + + public string PasswordSalt { get; set; } + + /// + /// The s for the + /// + public List InstanceUsers { get; set; } + + /// + /// The s for the + /// + public List Tokens { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs index 9daccbbde5..f5da5c1243 100644 --- a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -4,13 +4,17 @@ namespace Tgstation.Server.Host.Models { sealed class DreamDaemonSettings : Api.Models.Internal.DreamDaemonSettings { - long Id { get; set; } - - int? ProcessId { get; set; } + public long Id { get; set; } - long InstanceId { get; set; } + public int? ProcessId { get; set; } + + public long InstanceId { get; set; } + + public string AccessToken { get; set; } [Required] - Instance Instance { get; set; } + public Instance Instance { get; set; } + + new public DbCompileJob CompileJob { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index eaddc22b8a..43b58c39e1 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -4,9 +4,13 @@ namespace Tgstation.Server.Host.Models { sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings { - long Id { get; set; } - long InstanceId { get; set; } + public long Id { get; set; } + + public long InstanceId { get; set; } + [Required] - Instance Instance { get; set; } + public Instance Instance { get; set; } + + public DbCompileJob CompileJob { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 9330d3d175..647b50d83c 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -10,9 +10,9 @@ namespace Tgstation.Server.Host.Models interface IDatabaseContext { /// - /// The s in the + /// The s in the /// - DbSet Users { get; } + DbSet Users { get; } /// /// The s in the diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 6ebb7f8507..db1dfddde1 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -13,16 +13,19 @@ namespace Tgstation.Server.Host.Models /// The for the /// [Required] - ChatSettings ChatSettings { get; set; } + public ChatSettings ChatSettings { get; set; } /// /// The for the /// [Required] - DreamMakerSettings DreamMakerSettings { get; set; } + public DreamMakerSettings DreamMakerSettings { get; set; } [Required] - DreamDaemonSettings DreamDaemonSettings { get; set; } + public DreamDaemonSettings DreamDaemonSettings { get; set; } + + [Required] + public RepositorySettings RepositorySettings { get; set; } /// /// The s in the @@ -35,9 +38,9 @@ namespace Tgstation.Server.Host.Models public List TestMerges { get; set; } /// - /// The s in the + /// The s in the /// - public List CompileJobs { get; set; } + public List CompileJobs { get; set; } /// /// The in the diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs new file mode 100644 index 0000000000..7794b2d357 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -0,0 +1,7 @@ +namespace Tgstation.Server.Host.Models +{ + sealed class InstanceUser : Api.Models.InstanceUser + { + public long Id { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 2becf695ff..dd70b9bc04 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -5,8 +5,9 @@ namespace Tgstation.Server.Host.Models sealed class Job : Api.Models.Internal.Job { [Required] - new public User StartedBy { get; set; } + new public DbUser StartedBy { get; set; } + [Required] Instance Instance { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs new file mode 100644 index 0000000000..96412c401f --- /dev/null +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + sealed class RepositorySettings : Api.Models.Internal.RepositorySettings + { + public long Id { get; set; } + + public long InstanceId { get; set; } + + [Required] + public Instance Instance { get; set; } + + new public RevisionInformation RevisionInformation { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs new file mode 100644 index 0000000000..525a243d43 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Tgstation.Server.Host.Models +{ + sealed class RevisionInformation : Api.Models.RevisionInformation + { + public long Id { get; set; } + + new public List TestMerges { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/ServerSettings.cs b/src/Tgstation.Server.Host/Models/ServerSettings.cs index 60576514ec..8a84475dad 100644 --- a/src/Tgstation.Server.Host/Models/ServerSettings.cs +++ b/src/Tgstation.Server.Host/Models/ServerSettings.cs @@ -1,7 +1,9 @@ -namespace Tgstation.Server.Host.Models +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models { sealed class ServerSettings : Api.Models.Internal.ServerSettings { - long Id { get; set; } + public long Id { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index d542473e5a..eac3df5d9f 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -8,6 +8,6 @@ namespace Tgstation.Server.Host.Models sealed class TestMerge : Api.Models.TestMerge { [Required] - new User MergedBy { get; set; } + new public DbUser MergedBy { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs deleted file mode 100644 index 743ee5e8db..0000000000 --- a/src/Tgstation.Server.Host/Models/User.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using Tgstation.Server.Api.Models; - -namespace Tgstation.Server.Host.Models -{ - /// - /// Represents a in the database - /// - sealed class User : Api.Models.User - { - /// - /// The s for the - /// - List InstanceUsers { get; set; } - - /// - /// The s for the - /// - List Tokens { get; set; } - } -} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index bc0d5b6b3f..33b9c10d67 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -23,14 +23,20 @@ - - - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + - + diff --git a/src/Tgstation.Server.Host/appsettings.Development.json b/src/Tgstation.Server.Host/appsettings.Development.json index d3796e9ae8..1350bf6fde 100644 --- a/src/Tgstation.Server.Host/appsettings.Development.json +++ b/src/Tgstation.Server.Host/appsettings.Development.json @@ -19,6 +19,6 @@ }, "Database": { "DatabaseType": "Sqlite", - "ConnectionString": "Data Source=TestDB.sqlite3;Version=3;" + "ConnectionString": "Data Source=TestDB.sqlite3" } } From f9799ca51bcef4e7f6a1973b0ad0e6b39d47a320 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Apr 2018 11:07:30 -0400 Subject: [PATCH 074/397] Progress --- src/Tgstation.Server.Api/Models/CompileJob.cs | 39 +------------ .../Models/DreamDaemon.cs | 10 +++- .../Models/Internal/CompileJob.cs | 39 +++++++++++++ .../Models/Internal/DreamDaemonSettings.cs | 9 +-- .../Models/Internal/Job.cs | 3 - .../Models/Internal/RepositorySettings.cs | 4 -- .../Models/Internal/RevisionInformation.cs | 13 +++++ .../Models/Internal/TestMerge.cs | 22 +++++++ .../Models/Internal/Token.cs | 47 --------------- src/Tgstation.Server.Api/Models/Job.cs | 2 + src/Tgstation.Server.Api/Models/Repository.cs | 3 + .../Models/RevisionInformation.cs | 12 +--- src/Tgstation.Server.Api/Models/TestMerge.cs | 21 +------ src/Tgstation.Server.Api/Models/Token.cs | 20 +------ .../Tgstation.Server.Api.csproj | 7 +-- .../Components/ITokenClient.cs | 57 ------------------- src/Tgstation.Server.Client/IServerClient.cs | 5 -- .../Core/AuthenticationContext.cs | 2 +- .../Core/IAuthenticationContext.cs | 2 +- .../Models/CompileJob.cs | 13 +++++ .../Models/DatabaseContext.cs | 6 +- .../Models/DbCompileJob.cs | 13 ----- .../Models/DreamDaemonSettings.cs | 6 +- .../Models/DreamMakerSettings.cs | 2 +- .../Models/IDatabaseContext.cs | 4 +- src/Tgstation.Server.Host/Models/Instance.cs | 4 +- src/Tgstation.Server.Host/Models/Job.cs | 4 +- .../Models/RepositorySettings.cs | 2 +- .../Models/RevisionInformation.cs | 4 +- .../Models/ServerSettings.cs | 6 ++ src/Tgstation.Server.Host/Models/TestMerge.cs | 4 +- .../Models/{DbUser.cs => User.cs} | 10 +--- .../Tgstation.Server.Host.csproj | 13 ++--- ...Tgstation.Server.Host.Service.Tests.csproj | 2 +- 34 files changed, 145 insertions(+), 265 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Internal/CompileJob.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/TestMerge.cs delete mode 100644 src/Tgstation.Server.Api/Models/Internal/Token.cs delete mode 100644 src/Tgstation.Server.Client/Components/ITokenClient.cs create mode 100644 src/Tgstation.Server.Host/Models/CompileJob.cs delete mode 100644 src/Tgstation.Server.Host/Models/DbCompileJob.cs rename src/Tgstation.Server.Host/Models/{DbUser.cs => User.cs} (55%) diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 4f1592a25e..4478dde763 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -1,46 +1,11 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { - public class CompileJob + public sealed class CompileJob : Internal.CompileJob { - /// - /// The ID of the job - /// - public long Id { get; set; } - /// /// The that triggered the job /// public User TriggeredBy { get; set; } - - /// - /// When the compilation started - /// - [Required] - public DateTimeOffset StartedAt { get; set; } - - /// - /// When the compilation finished - /// - public DateTimeOffset FinishedAt { get; set; } - - /// - /// If the compiler targeted the primary directory - /// - public bool? TargetedPrimaryDirectory { get; set; } - - /// - /// Textual output of DM - /// - public string Output { get; set; } - - /// - /// Exit code of DM. If , the job was cancelled - /// - public int? ExitCode { get; set; } /// /// Git revision the compiler ran on. Not modifiable diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index b6fff22b40..2c48828bbf 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models @@ -11,6 +9,12 @@ namespace Tgstation.Server.Api.Models [Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)] public sealed class DreamDaemon : DreamDaemonSettings { + /// + /// When the live revision was compiled + /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] + public CompileJob CompileJob { get; set; } + /// /// The current status of /// diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs new file mode 100644 index 0000000000..b9af1662d6 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -0,0 +1,39 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models.Internal +{ + public class CompileJob + { + /// + /// The ID of the job + /// + public long Id { get; set; } + + /// + /// When the compilation started + /// + [Required] + public DateTimeOffset StartedAt { get; set; } + + /// + /// When the compilation finished + /// + public DateTimeOffset FinishedAt { get; set; } + + /// + /// If the compiler targeted the primary directory + /// + public bool? TargetedPrimaryDirectory { get; set; } + + /// + /// Textual output of DM + /// + public string Output { get; set; } + + /// + /// Exit code of DM. If , the job was cancelled + /// + public int? ExitCode { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index 6288508a43..610d6b59a1 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -1,16 +1,9 @@ -using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { public class DreamDaemonSettings { - /// - /// When the live revision was compiled - /// - [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] - public CompileJob CompileJob { get; set; } - /// /// If starts when it's starts /// diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index 26266a20c5..0effd5cbfc 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -22,9 +22,6 @@ namespace Tgstation.Server.Api.Models.Internal [Required] public DateTimeOffset StartedAt { get; set; } - [Required] - public User StartedBy { get; set; } - /// /// When the stopped /// diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index 426ad618d2..1d50db143c 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal @@ -11,9 +10,6 @@ namespace Tgstation.Server.Api.Models.Internal /// [Permissions(WriteRight = RepositoryRights.SetOrigin)] public string Origin { get; set; } - - [Permissions(DenyWrite = true)] - public RevisionInformation RevisionInformation { get; set; } /// /// The name of the committer diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs new file mode 100644 index 0000000000..5914d3d962 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models.Internal +{ + public class RevisionInformation + { + [Required, StringLength(40)] + public string Revision { get; set; } + + [Required, StringLength(40)] + public string OriginRevision { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs b/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs new file mode 100644 index 0000000000..9db2ffcb83 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models.Internal +{ + public class TestMerge + { + public long Id { get; set; } + + [Required] + public DateTimeOffset MergedAt { get; set; } + + [Required] + public string TitleAtMerge { get; set; } + + [Required] + public string BodyAtMerge { get; set; } + + [Required] + public string Author { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Internal/Token.cs b/src/Tgstation.Server.Api/Models/Internal/Token.cs deleted file mode 100644 index 3b1cad7574..0000000000 --- a/src/Tgstation.Server.Api/Models/Internal/Token.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Rights; - -namespace Tgstation.Server.Api.Models.Internal -{ - /// - /// Represents an access token for the server. Read action generates a new one. Update action expires all for user - /// - [Model(RightsType.Token, CanList = true)] - public class Token - { - /// - /// The id of the . Not modifiable - /// - public long Id { get; set; } - - /// - /// The user agent that created the - /// - [Required] - public string ClientUserAgent { get; set; } - - [Required, MinLength(4), MaxLength(16)] - public byte[] IssuedTo { get; set; } - - [Required, MinLength(4), MaxLength(16)] - public byte[] LastUsedBy { get; set; } - - /// - /// When the was originally issued - /// - [Required] - public DateTimeOffset IssuedAt { get; set; } - - /// - /// When the was last used - /// - public DateTimeOffset LastUsedAt { get; set; } - - /// - /// The token . Not modifiable, only appears once - /// - [Required] - public string Value { get; set; } - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs index e5c23eaa40..959d546962 100644 --- a/src/Tgstation.Server.Api/Models/Job.cs +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -15,5 +15,7 @@ /// If the current user has permission to cancel the job /// public bool UserCanCancel { get; set; } + + public User StartedBy { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 81e796ef00..fbc279d226 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -15,6 +15,9 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = RepositoryRights.SetSha)] public string NewRevision { get; set; } + [Permissions(DenyWrite = true)] + public RevisionInformation RevisionInformation { get; set; } + /// /// The branch or tag HEAD points to /// diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs index ea7165b24b..14f5fffa0a 100644 --- a/src/Tgstation.Server.Api/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs @@ -1,17 +1,9 @@ using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; namespace Tgstation.Server.Api.Models { - public class RevisionInformation - { - [Required, StringLength(40)] - public string Revision { get; set; } - - [Required, StringLength(40)] - public string OriginRevision { get; set; } - + public sealed class RevisionInformation : Internal.RevisionInformation + { public List TestMerges { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/TestMerge.cs b/src/Tgstation.Server.Api/Models/TestMerge.cs index 1cb64db9ea..706b0b58a8 100644 --- a/src/Tgstation.Server.Api/Models/TestMerge.cs +++ b/src/Tgstation.Server.Api/Models/TestMerge.cs @@ -1,24 +1,7 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { - public class TestMerge : TestMergeParameters + public sealed class TestMerge : Internal.TestMerge { - public long Id { get; set; } - - [Required] - public DateTimeOffset MergedAt { get; set; } - public User MergedBy { get; set; } - - [Required] - public string TitleAtMerge { get; set; } - - [Required] - public string BodyAtMerge { get; set; } - - [Required] - public string Author { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index b709bea3d8..6e806cf9a6 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,22 +1,8 @@ -using System.Net; - -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// - public sealed class Token : Internal.Token + public sealed class Token { - /// - new public IPAddress IssuedTo - { - get => new IPAddress(base.IssuedTo); - set => base.IssuedTo = value.GetAddressBytes(); - } - - /// - new public IPAddress LastUsedBy - { - get => new IPAddress(base.LastUsedBy); - set => base.LastUsedBy = value.GetAddressBytes(); - } + public string Value { get; set; } } } diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 51641091d2..77d9900e91 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -20,12 +20,7 @@ - - - - - ..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\system.componentmodel.annotations\4.4.0\ref\netstandard2.0\System.ComponentModel.Annotations.dll - + diff --git a/src/Tgstation.Server.Client/Components/ITokenClient.cs b/src/Tgstation.Server.Client/Components/ITokenClient.cs deleted file mode 100644 index 03965132a0..0000000000 --- a/src/Tgstation.Server.Client/Components/ITokenClient.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Rights; - -namespace Tgstation.Server.Client.Components -{ - /// - /// for managing s - /// - public interface ITokenClient: IRightsClient - { - /// - /// Generate a new for the connected user/ipaddress, deleting the current one if applicable - /// - /// The for the operation - /// A resulting in the new - Task Read(CancellationToken cancellationToken); - - /// - /// Gets all active s for the - /// - /// The for the operation - /// A resulting a of active s for the - Task> GetClientInfos(CancellationToken cancellationToken); - - /// - /// Gets all active s for all users - /// - /// The for the operation - /// A resulting a of active s for the keyed by username - Task> GetAllInfos(CancellationToken cancellationToken); - - /// - /// Invalidate the specified active - /// - /// The to invalidate - /// The for the operation - /// A representing the running operation - Task Invalidate(Token token, CancellationToken cancellationToken); - - /// - /// Invalidate all active s for a - /// - /// The for the operation - /// A representing the running operation - Task InvalidateAllClient(CancellationToken cancellationToken); - - /// - /// Invalidate all active s - /// - /// The for the operation - /// A representing the running operation - Task InvalidateAll(CancellationToken cancellationToken); - } -} diff --git a/src/Tgstation.Server.Client/IServerClient.cs b/src/Tgstation.Server.Client/IServerClient.cs index aa0fb7cc39..510d609aeb 100644 --- a/src/Tgstation.Server.Client/IServerClient.cs +++ b/src/Tgstation.Server.Client/IServerClient.cs @@ -30,11 +30,6 @@ namespace Tgstation.Server.Client /// int RequeryRate { get; set; } - /// - /// Access the - /// - ITokenClient Tokens { get; } - /// /// Access the /// diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs index fe4c5f9b8d..aa8235f1b8 100644 --- a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs @@ -99,7 +99,7 @@ namespace Tgstation.Server.Host.Core public void Dispose() => SystemIdentity.Dispose(); /// - public Task User(CancellationToken cancellationToken) + public Task User(CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs index a198c93ceb..1d2466bbaf 100644 --- a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Core /// /// The for the operation /// A resulting in the represented by the - Task User(CancellationToken cancellationToken); + Task User(CancellationToken cancellationToken); /// /// The represented by and a given diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs new file mode 100644 index 0000000000..9e26c011e0 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + sealed class CompileJob : Api.Models.Internal.CompileJob + { + [Required] + public User TriggeredBy { get; set; } + + [Required] + public RevisionInformation RevisionInformation { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 677733bc83..5880046f97 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Models public DbSet ServerSettings { get; set; } /// - public DbSet Users { get; set; } + public DbSet Users { get; set; } /// public DbSet Instances { get; set; } @@ -31,9 +31,8 @@ namespace Tgstation.Server.Host.Models public DbSet ChatSettings { get; set; } public DbSet DreamDaemonSettings { get; set; } public DbSet DreamMakerSettings { get; set; } - public DbSet CompileJobs { get; set; } + public DbSet CompileJobs { get; set; } public DbSet Jobs { get; set; } - public DbSet Tokens { get; set; } public DbSet TestMerges { get; set; } public DbSet RevisionInformations { get; set; } public DbSet RepositorySettings { get; set; } @@ -75,6 +74,7 @@ namespace Tgstation.Server.Host.Models modelBuilder.Entity().ToTable(nameof(Logs)); modelBuilder.Entity().HasIndex(x => x.Revision).IsUnique(); + modelBuilder.Entity().HasIndex(x => x.SystemIdentifier).IsUnique(); } /// diff --git a/src/Tgstation.Server.Host/Models/DbCompileJob.cs b/src/Tgstation.Server.Host/Models/DbCompileJob.cs deleted file mode 100644 index 6392c06024..0000000000 --- a/src/Tgstation.Server.Host/Models/DbCompileJob.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Tgstation.Server.Host.Models -{ - sealed class DbCompileJob : Api.Models.CompileJob - { - [Required] - new public DbUser TriggeredBy { get; set; } - - [Required] - new public RevisionInformation RevisionInformation { get; set; } - } -} diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs index f5da5c1243..2573093598 100644 --- a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -8,13 +8,13 @@ namespace Tgstation.Server.Host.Models public int? ProcessId { get; set; } - public long InstanceId { get; set; } - public string AccessToken { get; set; } + public long InstanceId { get; set; } + [Required] public Instance Instance { get; set; } - new public DbCompileJob CompileJob { get; set; } + public CompileJob CompileJob { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 43b58c39e1..fb79315315 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -11,6 +11,6 @@ namespace Tgstation.Server.Host.Models [Required] public Instance Instance { get; set; } - public DbCompileJob CompileJob { get; set; } + public CompileJob CompileJob { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 647b50d83c..9330d3d175 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -10,9 +10,9 @@ namespace Tgstation.Server.Host.Models interface IDatabaseContext { /// - /// The s in the + /// The s in the /// - DbSet Users { get; } + DbSet Users { get; } /// /// The s in the diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index db1dfddde1..0ecb141755 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -38,9 +38,9 @@ namespace Tgstation.Server.Host.Models public List TestMerges { get; set; } /// - /// The s in the + /// The s in the /// - public List CompileJobs { get; set; } + public List CompileJobs { get; set; } /// /// The in the diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index dd70b9bc04..e902cd9b96 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -5,9 +5,9 @@ namespace Tgstation.Server.Host.Models sealed class Job : Api.Models.Internal.Job { [Required] - new public DbUser StartedBy { get; set; } + public User StartedBy { get; set; } [Required] - Instance Instance { get; set; } + public Instance Instance { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 96412c401f..782ef405db 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -11,6 +11,6 @@ namespace Tgstation.Server.Host.Models [Required] public Instance Instance { get; set; } - new public RevisionInformation RevisionInformation { get; set; } + public RevisionInformation RevisionInformation { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 525a243d43..5d5b9a4885 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -2,10 +2,10 @@ namespace Tgstation.Server.Host.Models { - sealed class RevisionInformation : Api.Models.RevisionInformation + sealed class RevisionInformation : Api.Models.Internal.RevisionInformation { public long Id { get; set; } - new public List TestMerges { get; set; } + public List TestMerges { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/ServerSettings.cs b/src/Tgstation.Server.Host/Models/ServerSettings.cs index 8a84475dad..365d9b3aa2 100644 --- a/src/Tgstation.Server.Host/Models/ServerSettings.cs +++ b/src/Tgstation.Server.Host/Models/ServerSettings.cs @@ -5,5 +5,11 @@ namespace Tgstation.Server.Host.Models sealed class ServerSettings : Api.Models.Internal.ServerSettings { public long Id { get; set; } + + /// + /// The value used for the to encrypt JWTs + /// + [Required] + public byte[] TokenSecret { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index eac3df5d9f..b334c2b844 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -5,9 +5,9 @@ namespace Tgstation.Server.Host.Models /// /// Represent a merge of a live pull request /// - sealed class TestMerge : Api.Models.TestMerge + sealed class TestMerge : Api.Models.Internal.TestMerge { [Required] - new public DbUser MergedBy { get; set; } + public User MergedBy { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DbUser.cs b/src/Tgstation.Server.Host/Models/User.cs similarity index 55% rename from src/Tgstation.Server.Host/Models/DbUser.cs rename to src/Tgstation.Server.Host/Models/User.cs index 401ce517be..149768c00d 100644 --- a/src/Tgstation.Server.Host/Models/DbUser.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -1,25 +1,19 @@ using System.Collections.Generic; -using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Models { /// /// Represents a in the database /// - sealed class DbUser : Api.Models.User + sealed class User : Api.Models.User { public string PasswordHash { get; set; } public string PasswordSalt { get; set; } /// - /// The s for the + /// The s for the /// public List InstanceUsers { get; set; } - - /// - /// The s for the - /// - public List Tokens { get; set; } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 33b9c10d67..3b031e0289 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -37,15 +37,14 @@ - - - - - + + + + + - - + diff --git a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj index fdbb9cfe04..ccb09b4c3c 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj +++ b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj @@ -75,7 +75,7 @@ Tgstation.Server.Host.Service - {5D2D682C-6BF0-439C-850B-6AB945BBEAEA} + {5d2d682c-6bf0-439c-850b-6ab945bbeaea} Tgstation.Server.Host.Watchdog From 1d329c16058fd64ea0d41503c647b8f3d98dfbf5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Apr 2018 13:22:58 -0400 Subject: [PATCH 075/397] Slowly coming together --- .../Models/Administration.cs | 1 - .../Models/ChatChannel.cs | 7 +- .../Models/ChatSettings.cs | 50 ++----------- .../Models/Internal/ChatSettings.cs | 51 ++++++++++++++ .../Models/Internal/ServerSettings.cs | 1 + .../Models/Internal/User.cs | 48 +++++++++++++ src/Tgstation.Server.Api/Models/User.cs | 44 +----------- .../Rights/RightsHelper.cs | 1 - src/Tgstation.Server.Api/Rights/RightsType.cs | 5 -- .../Rights/TokenRights.cs | 32 --------- src/Tgstation.Server.Host/Core/Application.cs | 5 ++ .../Core/CryptographySuite.cs | 70 +++++++++++++++++++ .../Core/ICryptographySuite.cs | 23 ++++++ .../Models/ChatChannel.cs | 10 +++ .../Models/ChatSettings.cs | 7 +- .../Models/DatabaseContext.cs | 28 +++++++- .../Models/DatabaseSeeder.cs | 55 +++++++++++++++ .../Models/IDatabaseSeeder.cs | 19 +++++ .../Models/ServerSettings.cs | 6 -- src/Tgstation.Server.Host/Models/User.cs | 10 ++- .../Tgstation.Server.Host.csproj | 1 + 21 files changed, 328 insertions(+), 146 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/User.cs delete mode 100644 src/Tgstation.Server.Api/Rights/TokenRights.cs create mode 100644 src/Tgstation.Server.Host/Core/CryptographySuite.cs create mode 100644 src/Tgstation.Server.Host/Core/ICryptographySuite.cs create mode 100644 src/Tgstation.Server.Host/Models/ChatChannel.cs create mode 100644 src/Tgstation.Server.Host/Models/DatabaseSeeder.cs create mode 100644 src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 1aed2dad16..374d82a178 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -7,7 +7,6 @@ namespace Tgstation.Server.Api.Models /// /// Metadata about an installation /// - [Model(RightsType.Administration)] public sealed class Administration : Internal.ServerSettings { /// diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index 737b41203d..1ad3c74629 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -3,13 +3,8 @@ /// /// Indicates a chat channel /// - public sealed class ChatChannel + public class ChatChannel { - /// - /// The column ID - /// - public long Id { get; set; } - /// /// The IRC channel name /// diff --git a/src/Tgstation.Server.Api/Models/ChatSettings.cs b/src/Tgstation.Server.Api/Models/ChatSettings.cs index 593e6c4a8c..a6350f0aa3 100644 --- a/src/Tgstation.Server.Api/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/ChatSettings.cs @@ -1,63 +1,21 @@ using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { - /// - /// Manage the server chat bots - /// - [Model(RightsType.Chat, RequiresInstance = true)] - public class ChatSettings + /// + public sealed class ChatSettings : Internal.ChatSettings { - /// - /// If the IRC client is enabled - /// - [Permissions(WriteRight = ChatSettingsRights.SetIrcEnabled)] - public bool IrcEnabled { get; set; } - - /// - /// The IRC server name - /// - [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] - [Required] - public string IrcHost { get; set; } - - /// - /// The IRC server port - /// - [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] - public ushort IrcPort { get; set; } - - /// - /// The IRC server NickServ password - /// - [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] - public string IrcNickServPassword { get; set; } - - /// - /// If the Discord bot is enabled - /// - [Permissions(WriteRight = ChatSettingsRights.SetDiscordEnabled)] - public bool DiscordEnabled { get; set; } - - /// - /// The Discord bot token - /// - [Permissions(ReadRight = ChatSettingsRights.SetDiscordSettings, WriteRight = ChatSettingsRights.SetDiscordSettings)] - public string DiscordBotToken { get; set; } - /// /// Channels the bot should listen/announce in and allow admin commands /// [Permissions(WriteRight = ChatSettingsRights.SetDiscordChannels)] - public virtual List AdminChannels { get; set; } + public List AdminChannels { get; set; } /// /// Channels the Discord bot should listen/announce in /// [Permissions(WriteRight = ChatSettingsRights.SetDiscordChannels)] - public virtual List GeneralChannels { get; set; } + public List GeneralChannels { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs new file mode 100644 index 0000000000..b98d62b906 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + /// + /// Manage the server chat bots + /// + [Model(RightsType.Chat, RequiresInstance = true)] + public class ChatSettings + { + /// + /// If the IRC client is enabled + /// + [Permissions(WriteRight = ChatSettingsRights.SetIrcEnabled)] + public bool IrcEnabled { get; set; } + + /// + /// The IRC server name + /// + [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] + [Required] + public string IrcHost { get; set; } + + /// + /// The IRC server port + /// + [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] + public ushort IrcPort { get; set; } + + /// + /// The IRC server NickServ password + /// + [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] + public string IrcNickServPassword { get; set; } + + /// + /// If the Discord bot is enabled + /// + [Permissions(WriteRight = ChatSettingsRights.SetDiscordEnabled)] + public bool DiscordEnabled { get; set; } + + /// + /// The Discord bot token + /// + [Permissions(ReadRight = ChatSettingsRights.SetDiscordSettings, WriteRight = ChatSettingsRights.SetDiscordSettings)] + public string DiscordBotToken { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs index 7aa96da12b..9a46e42237 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs @@ -2,6 +2,7 @@ namespace Tgstation.Server.Api.Models.Internal { + [Model(RightsType.Administration)] public class ServerSettings { /// diff --git a/src/Tgstation.Server.Api/Models/Internal/User.cs b/src/Tgstation.Server.Api/Models/Internal/User.cs new file mode 100644 index 0000000000..0368a0d1b9 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/User.cs @@ -0,0 +1,48 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + /// + /// Represents a server + /// + [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers)] + public class User + { + /// + /// The ID of the + /// + [Permissions(DenyWrite = true)] + public long Id { get; set; } + + [Permissions(DenyWrite = true)] + [Required] + public DateTimeOffset CreatedAt { get; set; } + + /// + /// The SID/UID of the on Windows/POSIX respectively + /// + [Permissions(DenyWrite = true)] + public string SystemIdentifier { get; set; } + + /// + /// The name of the + /// + [Permissions(WriteRight = AdministrationRights.EditUsers)] + [Required] + public string Name { get; set; } + + /// + /// The for the + /// + [Permissions(WriteRight = AdministrationRights.EditUsers)] + public AdministrationRights AdministrationRights { get; set; } + + /// + /// The for the + /// + [Permissions(WriteRight = AdministrationRights.EditUsers)] + public InstanceManagerRights InstanceManagerRights { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index 70bd28bed1..bb8c604138 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -1,51 +1,11 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Rights; - -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// /// Represents a server /// - [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers)] - public class User + public sealed class User : Internal.User { - /// - /// The ID of the - /// - [Permissions(DenyWrite = true)] - public long Id { get; set; } - - [Permissions(DenyWrite = true)] - [Required] - public DateTimeOffset CreatedAt { get; set; } - - /// - /// The SID/UID of the on Windows/POSIX respectively - /// - [Permissions(DenyWrite = true)] - public string SystemIdentifier { get; set; } - [Permissions(DenyWrite = true)] public bool Enabled { get; set; } - - /// - /// The name of the - /// - [Permissions(WriteRight = AdministrationRights.EditUsers)] - [Required] - public string Name { get; set; } - - /// - /// The for the - /// - [Permissions(WriteRight = AdministrationRights.EditUsers)] - public AdministrationRights AdministrationRights { get; set; } - - /// - /// The for the - /// - [Permissions(WriteRight = AdministrationRights.EditUsers)] - public InstanceManagerRights InstanceManagerRights { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index 0fc6e5efcc..0f754c4d13 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -15,7 +15,6 @@ namespace Tgstation.Server.Api.Rights { { RightsType.Administration, typeof(AdministrationRights) }, { RightsType.InstanceManager, typeof(InstanceManagerRights) }, - { RightsType.Token, typeof(TokenRights) }, { RightsType.Repository, typeof(RepositoryRights) }, { RightsType.Byond, typeof(ByondRights) }, diff --git a/src/Tgstation.Server.Api/Rights/RightsType.cs b/src/Tgstation.Server.Api/Rights/RightsType.cs index a15d9d2d03..5b4ff0722c 100644 --- a/src/Tgstation.Server.Api/Rights/RightsType.cs +++ b/src/Tgstation.Server.Api/Rights/RightsType.cs @@ -13,11 +13,6 @@ /// /// InstanceManager, - /// - /// - /// - Token, - /// /// /// diff --git a/src/Tgstation.Server.Api/Rights/TokenRights.cs b/src/Tgstation.Server.Api/Rights/TokenRights.cs deleted file mode 100644 index 98f5bf3f03..0000000000 --- a/src/Tgstation.Server.Api/Rights/TokenRights.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace Tgstation.Server.Api.Rights -{ - /// - /// Rights for s - /// - [Flags] - public enum TokenRights - { - /// - /// User has no rights - /// - None = 0, - /// - /// User can create s - /// - Create = 1, - /// - /// User can delete s - /// - Delete = 2, - /// - /// User can list all their s - /// - List = 4, - /// - /// User can perform all the actions they can for themselves on behalf of other users except - /// - Admin = 8, - } -} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 2a8d4f6f78..3e392b41d2 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,6 +1,7 @@ using Cyberboss.AspNetCore.AsyncInitializer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; @@ -46,6 +47,10 @@ namespace Tgstation.Server.Host.Core services.AddDbContext(); services.AddScoped(x => x.GetRequiredService()); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton, PasswordHasher>(); } /// diff --git a/src/Tgstation.Server.Host/Core/CryptographySuite.cs b/src/Tgstation.Server.Host/Core/CryptographySuite.cs new file mode 100644 index 0000000000..9cd7294780 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/CryptographySuite.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Security.Cryptography; +using System.Text; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class CryptographySuite : ICryptographySuite + { + /// + /// The length of secure strings used in the application + /// + public const int SecureStringLength = 40; + + /// + /// Generates a secure ascii of length + /// + /// A secure ascii of length + static string GenerateSecureString() + { + using (var rng = new RNGCryptoServiceProvider()) + { + var byt = new byte[1]; + var result = new StringBuilder + { + Capacity = SecureStringLength + }; + while (result.Length < SecureStringLength) + { + rng.GetBytes(byt); + var chr = (char)byt[0]; + if (Char.IsLetterOrDigit(chr)) + result.Append(chr); + } + return result.ToString(); + } + } + + /// + /// The for the + /// + readonly IPasswordHasher passwordHasher; + + /// + /// Construct a + /// + /// The value of + public CryptographySuite(IPasswordHasher passwordHasher) => this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); + + /// + public void RegenerateUserToken(User user) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + user.TokenSecret = GenerateSecureString(); + } + + /// + public void SetUserPassword(User user, string newPassword) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + if (String.IsNullOrEmpty(newPassword)) + throw new ArgumentNullException(nameof(newPassword)); + user.PasswordHash = passwordHasher.HashPassword(user, newPassword); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/ICryptographySuite.cs b/src/Tgstation.Server.Host/Core/ICryptographySuite.cs new file mode 100644 index 0000000000..f773c17301 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ICryptographySuite.cs @@ -0,0 +1,23 @@ +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Contains various cryptographic functions + /// + interface ICryptographySuite + { + /// + /// Sets a for a given + /// + /// The whos is to be set + /// The new password for the + void SetUserPassword(User user, string newPassword); + + /// + /// Sets a new for a givent + /// + /// The whos is to be set + void RegenerateUserToken(User user); + } +} diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs new file mode 100644 index 0000000000..8429585cdf --- /dev/null +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -0,0 +1,10 @@ +namespace Tgstation.Server.Host.Models +{ + sealed class ChatChannel : Api.Models.ChatChannel + { + /// + /// The column ID + /// + public long Id { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs index 0b798988c0..c62d8e23dd 100644 --- a/src/Tgstation.Server.Host/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { - sealed class ChatSettings : Api.Models.ChatSettings + sealed class ChatSettings : Api.Models.Internal.ChatSettings { public long Id { get; set; } @@ -12,5 +11,9 @@ namespace Tgstation.Server.Host.Models [Required] public Instance Instance { get; set; } + + public List AdminChannels { get; set; } + + public List GeneralChannels { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 5880046f97..1a6cf81e00 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -3,10 +3,14 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; +using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Core; using ZNetCS.AspNetCore.Logging.EntityFrameworkCore; namespace Tgstation.Server.Host.Models @@ -49,6 +53,10 @@ namespace Tgstation.Server.Host.Models /// The for the /// readonly IHostingEnvironment hostingEnvironment; + /// + /// The for the + /// + readonly IDatabaseSeeder databaseSeeder; /// /// Construct a @@ -57,11 +65,13 @@ namespace Tgstation.Server.Host.Models /// The containing the value of /// The value of /// The value of - public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment) : base(dbContextOptions) + /// The value of + public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment, IDatabaseSeeder databaseSeeder) : base(dbContextOptions) { databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder)); } /// @@ -98,13 +108,25 @@ namespace Tgstation.Server.Host.Models } /// - public Task GetServerSettings(CancellationToken cancellationToken) => ServerSettings.FirstOrDefaultAsync(cancellationToken); + public async Task GetServerSettings(CancellationToken cancellationToken) + { + var settings = await ServerSettings.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (settings == default(ServerSettings)) + { + settings = new ServerSettings(); + ServerSettings.Add(settings); + } + return settings; + } /// public async Task Initialize(CancellationToken cancellationToken) { - await Database.EnsureCreatedAsync(cancellationToken).ConfigureAwait(false); + var migrations = await Database.GetAppliedMigrationsAsync().ConfigureAwait(false); + var wasEmpty = !migrations.Any(); await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); + if (wasEmpty) + await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs new file mode 100644 index 0000000000..278692554e --- /dev/null +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Models +{ + /// + sealed class DatabaseSeeder : IDatabaseSeeder + { + /// + /// The default password mode admin password + /// + const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"; + + /// + /// The default git repository to pull server updates from + /// + const string DefaultUpstreamRepository = "https://github.com/tgstation/tgstation-server"; + + /// + /// The for the + /// + readonly ICryptographySuite cryptographySuite; + + /// + /// Construct a + /// + /// The value of + public DatabaseSeeder(ICryptographySuite cryptographySuite) => this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + + /// + public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken) + { + var admin = new User + { + AdministrationRights = (AdministrationRights)~0, + CreatedAt = DateTimeOffset.Now, + InstanceManagerRights = (InstanceManagerRights)~0, + Name = "Admin" + }; + cryptographySuite.RegenerateUserToken(admin); + cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); + databaseContext.Users.Add(admin); + + var serverSettings = await databaseContext.GetServerSettings(cancellationToken).ConfigureAwait(false); + + serverSettings.EnableTelemetry = true; + serverSettings.UpstreamRepository = DefaultUpstreamRepository; + + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs new file mode 100644 index 0000000000..f57620fd0a --- /dev/null +++ b/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Models +{ + /// + /// For initially seeding a database + /// + interface IDatabaseSeeder + { + /// + /// Initially seed a given + /// + /// The to seed + /// The for the operation + /// A representing the running operation + Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Models/ServerSettings.cs b/src/Tgstation.Server.Host/Models/ServerSettings.cs index 365d9b3aa2..8a84475dad 100644 --- a/src/Tgstation.Server.Host/Models/ServerSettings.cs +++ b/src/Tgstation.Server.Host/Models/ServerSettings.cs @@ -5,11 +5,5 @@ namespace Tgstation.Server.Host.Models sealed class ServerSettings : Api.Models.Internal.ServerSettings { public long Id { get; set; } - - /// - /// The value used for the to encrypt JWTs - /// - [Required] - public byte[] TokenSecret { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index 149768c00d..be2d1ae09d 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -1,15 +1,21 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Models { /// /// Represents a in the database /// - sealed class User : Api.Models.User + sealed class User : Api.Models.Internal.User { public string PasswordHash { get; set; } - public string PasswordSalt { get; set; } + /// + /// The value used for the to encrypt JWTs + /// + [StringLength(CryptographySuite.SecureStringLength)] + public string TokenSecret { get; set; } /// /// The s for the diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 3b031e0289..f97f3f42ea 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -38,6 +38,7 @@ + From aba5e114bcbac9d9a15fc8370203de820ef25b72 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Apr 2018 16:48:23 -0400 Subject: [PATCH 076/397] Add migrations MySQL currently unsupported due to https://bugs.mysql.com/bug.php?id=89855 --- .gitignore | 1 + .../Configuration/DatabaseConfiguration.cs | 1 + src/Tgstation.Server.Host/Core/Application.cs | 49 +- .../Models/DatabaseContext.cs | 41 +- .../DesignTimeDbContextFactoryHelpers.cs | 29 + .../MySqlDesignTimeDbContextFactory.cs | 15 + .../20180410203936_InitialCreate.Designer.cs | 512 +++++++++++++++++ .../SqlServer/20180410203936_InitialCreate.cs | 537 ++++++++++++++++++ .../SqlServerDatabaseContextModelSnapshot.cs | 511 +++++++++++++++++ .../SqlServerDesignTimeDbContextFactory.cs | 15 + .../20180410204120_InitialCreate.Designer.cs | 510 +++++++++++++++++ .../Sqlite/20180410204120_InitialCreate.cs | 535 +++++++++++++++++ .../SqliteDatabaseContextModelSnapshot.cs | 509 +++++++++++++++++ .../SqliteDesignTimeDbContextFactory.cs | 15 + .../Models/MySqlDatabaseContext.cs | 30 + .../Models/SqlServerDatabaseContext.cs | 30 + .../Models/SqliteDatabaseContext.cs | 30 + .../Tgstation.Server.Host.csproj | 8 +- .../appsettings.Development.json | 24 - 19 files changed, 3339 insertions(+), 63 deletions(-) create mode 100644 src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.Designer.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqlServer/SqlServerDatabaseContextModelSnapshot.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.Designer.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs create mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs create mode 100644 src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs create mode 100644 src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs create mode 100644 src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs delete mode 100644 src/Tgstation.Server.Host/appsettings.Development.json diff --git a/.gitignore b/.gitignore index a0fa5f96e2..9b60069c91 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ artifacts/ *DS_Store *.sln.ide /TestResults +/src/Tgstation.Server.Host/appsettings.Development.json diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index b356408c50..ef28e52f8e 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -2,6 +2,7 @@ { sealed class DatabaseConfiguration { + public const string Section = "Database"; public DatabaseType DatabaseType { get; set; } public string ConnectionString { get; set; } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 3e392b41d2..b70e80286b 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; @@ -22,11 +23,21 @@ namespace Tgstation.Server.Host.Core /// readonly IConfiguration configuration; + /// + /// The for the + /// + readonly IHostingEnvironment hostingEnvironment; + /// /// Construct an /// /// The value of - public Application(IConfiguration configuration) => this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + /// The value of + public Application(IConfiguration configuration, IHostingEnvironment hostingEnvironment) + { + this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + } /// /// Configure dependency injected services @@ -39,15 +50,38 @@ namespace Tgstation.Server.Host.Core if (services == null) throw new ArgumentNullException(nameof(services)); var workingDir = Environment.CurrentDirectory; - services.Configure(configuration.GetSection("Database")); + var databaseConfigurationSection = configuration.GetSection(DatabaseConfiguration.Section); + services.Configure(databaseConfigurationSection); services.AddMvc(); services.AddOptions(); services.AddLocalization(); + + var databaseConfiguration = databaseConfigurationSection.Get(); + void ConfigureDatabase(DbContextOptionsBuilder builder) + { + if (hostingEnvironment.IsDevelopment()) + builder.EnableSensitiveDataLogging(); + }; - services.AddDbContext(); - services.AddScoped(x => x.GetRequiredService()); - + switch (databaseConfiguration.DatabaseType) + { + case DatabaseType.MySql: + services.AddDbContext(ConfigureDatabase); + services.AddScoped(x => x.GetRequiredService()); + break; + case DatabaseType.Sqlite: + services.AddDbContext(ConfigureDatabase); + services.AddScoped(x => x.GetRequiredService()); + break; + case DatabaseType.SqlServer: + services.AddDbContext(ConfigureDatabase); + services.AddScoped(x => x.GetRequiredService()); + break; + default: + throw new InvalidOperationException("Invalid DatabaseType!"); + } + services.AddSingleton(); services.AddSingleton(); services.AddSingleton, PasswordHasher>(); @@ -57,13 +91,10 @@ namespace Tgstation.Server.Host.Core /// Configure the /// /// The to configure - /// The of the - public void Configure(IApplicationBuilder applicationBuilder, IHostingEnvironment hostingEnvironment) + public void Configure(IApplicationBuilder applicationBuilder) { if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); - if (hostingEnvironment == null) - throw new ArgumentNullException(nameof(hostingEnvironment)); if (hostingEnvironment.IsDevelopment()) applicationBuilder.UseDeveloperExceptionPage(); diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 1a6cf81e00..9651b6b2db 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -1,21 +1,17 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Linq; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; -using Tgstation.Server.Host.Core; using ZNetCS.AspNetCore.Logging.EntityFrameworkCore; namespace Tgstation.Server.Host.Models { - sealed class DatabaseContext : DbContext, IDatabaseContext + /// + abstract class DatabaseContext : DbContext, IDatabaseContext where TParentContext : DbContext { /// public DbSet ServerSettings { get; set; } @@ -41,6 +37,11 @@ namespace Tgstation.Server.Host.Models public DbSet RevisionInformations { get; set; } public DbSet RepositorySettings { get; set; } + /// + /// The connection string for the + /// + protected string ConnectionString => databaseConfiguration.ConnectionString; + /// /// The for the /// @@ -50,10 +51,6 @@ namespace Tgstation.Server.Host.Models /// readonly ILoggerFactory loggerFactory; /// - /// The for the - /// - readonly IHostingEnvironment hostingEnvironment; - /// /// The for the /// readonly IDatabaseSeeder databaseSeeder; @@ -61,16 +58,14 @@ namespace Tgstation.Server.Host.Models /// /// Construct a /// - /// The for the + /// The for the /// The containing the value of /// The value of - /// The value of /// The value of - public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment, IDatabaseSeeder databaseSeeder) : base(dbContextOptions) + public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions) { databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder)); } @@ -90,21 +85,9 @@ namespace Tgstation.Server.Host.Models /// protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - switch (databaseConfiguration.DatabaseType) - { - case DatabaseType.MySql: - optionsBuilder.UseMySQL(databaseConfiguration.ConnectionString); - break; - case DatabaseType.Sqlite: - optionsBuilder.UseSqlite(databaseConfiguration.ConnectionString); - break; - case DatabaseType.SqlServer: - optionsBuilder.UseSqlServer(databaseConfiguration.ConnectionString); - break; - } + base.OnConfiguring(optionsBuilder); + optionsBuilder.UseLoggerFactory(loggerFactory); - if (hostingEnvironment.IsDevelopment()) - optionsBuilder.EnableSensitiveDataLogging(); } /// diff --git a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs new file mode 100644 index 0000000000..9c1da8c24e --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using System.IO; +using System.Reflection; +using Tgstation.Server.Host.Configuration; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// Contains helpers for creating design time s + /// + static class DesignTimeDbContextFactoryHelpers + { + /// + /// Path to the json file to use for migrations configuration + /// + const string MigrationsJson = "appsettings.Development.json"; + + /// + public static IOptions GetDbContextOptions() + { + var builder = new ConfigurationBuilder(); + builder.SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); + builder.AddJsonFile(MigrationsJson); + var configuration = builder.Build(); + return Options.Create(configuration.GetSection(DatabaseConfiguration.Section).Get()); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs new file mode 100644 index 0000000000..33640bd86f --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Logging; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + sealed class MySqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory + { + /// + public MySqlDatabaseContext CreateDbContext(string[] args) => new MySqlDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.Designer.cs new file mode 100644 index 0000000000..44d5df743e --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.Designer.cs @@ -0,0 +1,512 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Models.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20180410203936_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.0-preview2-30571") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("ChatSettingsId1"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId"); + + b.HasIndex("ChatSettingsId1"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DiscordBotToken"); + + b.Property("DiscordEnabled"); + + b.Property("InstanceId"); + + b.Property("IrcEnabled"); + + b.Property("IrcHost") + .IsRequired(); + + b.Property("IrcNickServPassword"); + + b.Property("IrcPort"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ExitCode"); + + b.Property("FinishedAt"); + + b.Property("InstanceId"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.Property("StartedAt"); + + b.Property("TargetedPrimaryDirectory"); + + b.Property("TriggeredById"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TriggeredById"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient"); + + b.Property("AutoStart"); + + b.Property("CompileJobId"); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart"); + + b.Property("SoftShutdown"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoCompileInterval"); + + b.Property("CompileJobId"); + + b.Property("InstanceId"); + + b.Property("TargetDme"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConfigurationAllowed"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online"); + + b.Property("Path") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatSettingsRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("RepositoryRights"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId"); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Cancelled"); + + b.Property("Description") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("StartedAt"); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdateInterval"); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("Origin"); + + b.Property("PushTestMergeCommits"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("OriginRevision") + .IsRequired() + .HasMaxLength(40); + + b.Property("Revision") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("Revision") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ServerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EnableTelemetry"); + + b.Property("SystemAuthenticationGroup"); + + b.Property("UpstreamRepository"); + + b.HasKey("Id"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("RevisionInformationId"); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("MergedById"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CreatedAt"); + + b.Property("InstanceManagerRights"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.Property("TokenSecret") + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("SystemIdentifier") + .IsUnique() + .HasFilter("[SystemIdentifier] IS NOT NULL"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ZNetCS.AspNetCore.Logging.EntityFrameworkCore.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EventId"); + + b.Property("Level"); + + b.Property("Message"); + + b.Property("Name") + .HasMaxLength(255); + + b.Property("TimeStamp"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatSettings") + .WithMany("AdminChannels") + .HasForeignKey("ChatSettingsId"); + + b.HasOne("Tgstation.Server.Host.Models.ChatSettings") + .WithMany("GeneralChannels") + .HasForeignKey("ChatSettingsId1"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("ChatSettings") + .HasForeignKey("Tgstation.Server.Host.Models.ChatSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("CompileJobs") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany() + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "TriggeredBy") + .WithMany() + .HasForeignKey("TriggeredById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany() + .HasForeignKey("RevisionInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("TestMerges") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany() + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation") + .WithMany("TestMerges") + .HasForeignKey("RevisionInformationId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.cs new file mode 100644 index 0000000000..0a9656c3c1 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.cs @@ -0,0 +1,537 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations.SqlServer +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Instances", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Path = table.Column(nullable: false), + Online = table.Column(nullable: false), + ConfigurationAllowed = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Instances", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Logs", + columns: table => new + { + EventId = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Level = table.Column(nullable: false), + Message = table.Column(nullable: true), + Name = table.Column(maxLength: 255, nullable: true), + TimeStamp = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Logs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RevisionInformations", + columns: table => new + { + Revision = table.Column(maxLength: 40, nullable: false), + OriginRevision = table.Column(maxLength: 40, nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn) + }, + constraints: table => + { + table.PrimaryKey("PK_RevisionInformations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ServerSettings", + columns: table => new + { + SystemAuthenticationGroup = table.Column(nullable: true), + EnableTelemetry = table.Column(nullable: false), + UpstreamRepository = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn) + }, + constraints: table => + { + table.PrimaryKey("PK_ServerSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + AdministrationRights = table.Column(nullable: false), + CreatedAt = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceManagerRights = table.Column(nullable: false), + SystemIdentifier = table.Column(nullable: true), + Name = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + TokenSecret = table.Column(maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ChatSettings", + columns: table => new + { + IrcEnabled = table.Column(nullable: false), + IrcHost = table.Column(nullable: false), + IrcPort = table.Column(nullable: false), + IrcNickServPassword = table.Column(nullable: true), + DiscordEnabled = table.Column(nullable: false), + DiscordBotToken = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatSettings", x => x.Id); + table.ForeignKey( + name: "FK_ChatSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RepositorySettings", + columns: table => new + { + Origin = table.Column(nullable: true), + CommitterName = table.Column(nullable: false), + CommitterEmail = table.Column(nullable: false), + AccessUser = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + PushTestMergeCommits = table.Column(nullable: false), + AutoUpdateInterval = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false), + RevisionInformationId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RepositorySettings", x => x.Id); + table.ForeignKey( + name: "FK_RepositorySettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RepositorySettings_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CompileJobs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + StartedAt = table.Column(nullable: false), + FinishedAt = table.Column(nullable: false), + TargetedPrimaryDirectory = table.Column(nullable: true), + Output = table.Column(nullable: true), + ExitCode = table.Column(nullable: true), + TriggeredById = table.Column(nullable: false), + RevisionInformationId = table.Column(nullable: false), + InstanceId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CompileJobs", x => x.Id); + table.ForeignKey( + name: "FK_CompileJobs_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CompileJobs_Users_TriggeredById", + column: x => x.TriggeredById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "InstanceUsers", + columns: table => new + { + ByondRights = table.Column(nullable: false), + DreamDaemonRights = table.Column(nullable: false), + DreamMakerRights = table.Column(nullable: false), + RepositoryRights = table.Column(nullable: false), + ChatSettingsRights = table.Column(nullable: false), + ConfigurationRights = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: true), + UserId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_InstanceUsers", x => x.Id); + table.ForeignKey( + name: "FK_InstanceUsers_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_InstanceUsers_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Jobs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Description = table.Column(nullable: false), + StartedAt = table.Column(nullable: false), + StoppedAt = table.Column(nullable: false), + Cancelled = table.Column(nullable: false), + StartedById = table.Column(nullable: false), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Jobs", x => x.Id); + table.ForeignKey( + name: "FK_Jobs_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Jobs_Users_StartedById", + column: x => x.StartedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "TestMerges", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + MergedAt = table.Column(nullable: false), + TitleAtMerge = table.Column(nullable: false), + BodyAtMerge = table.Column(nullable: false), + Author = table.Column(nullable: false), + MergedById = table.Column(nullable: false), + InstanceId = table.Column(nullable: true), + RevisionInformationId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TestMerges", x => x.Id); + table.ForeignKey( + name: "FK_TestMerges_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TestMerges_Users_MergedById", + column: x => x.MergedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_TestMerges_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ChatChannels", + columns: table => new + { + IrcChannel = table.Column(nullable: true), + DiscordChannelId = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ChatSettingsId = table.Column(nullable: true), + ChatSettingsId1 = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatChannels", x => x.Id); + table.ForeignKey( + name: "FK_ChatChannels_ChatSettings_ChatSettingsId", + column: x => x.ChatSettingsId, + principalTable: "ChatSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ChatChannels_ChatSettings_ChatSettingsId1", + column: x => x.ChatSettingsId1, + principalTable: "ChatSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "DreamDaemonSettings", + columns: table => new + { + AutoStart = table.Column(nullable: false), + AllowWebClient = table.Column(nullable: false), + SoftRestart = table.Column(nullable: false), + SoftShutdown = table.Column(nullable: false), + SecurityLevel = table.Column(nullable: false), + PrimaryPort = table.Column(nullable: false), + SecondaryPort = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ProcessId = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + InstanceId = table.Column(nullable: false), + CompileJobId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id); + table.ForeignKey( + name: "FK_DreamDaemonSettings_CompileJobs_CompileJobId", + column: x => x.CompileJobId, + principalTable: "CompileJobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_DreamDaemonSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DreamMakerSettings", + columns: table => new + { + AutoCompileInterval = table.Column(nullable: true), + TargetDme = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(nullable: false), + CompileJobId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DreamMakerSettings", x => x.Id); + table.ForeignKey( + name: "FK_DreamMakerSettings_CompileJobs_CompileJobId", + column: x => x.CompileJobId, + principalTable: "CompileJobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_DreamMakerSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ChatChannels_ChatSettingsId", + table: "ChatChannels", + column: "ChatSettingsId"); + + migrationBuilder.CreateIndex( + name: "IX_ChatChannels_ChatSettingsId1", + table: "ChatChannels", + column: "ChatSettingsId1"); + + migrationBuilder.CreateIndex( + name: "IX_ChatSettings_InstanceId", + table: "ChatSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_InstanceId", + table: "CompileJobs", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_RevisionInformationId", + table: "CompileJobs", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_TriggeredById", + table: "CompileJobs", + column: "TriggeredById"); + + migrationBuilder.CreateIndex( + name: "IX_DreamDaemonSettings_CompileJobId", + table: "DreamDaemonSettings", + column: "CompileJobId"); + + migrationBuilder.CreateIndex( + name: "IX_DreamDaemonSettings_InstanceId", + table: "DreamDaemonSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DreamMakerSettings_CompileJobId", + table: "DreamMakerSettings", + column: "CompileJobId"); + + migrationBuilder.CreateIndex( + name: "IX_DreamMakerSettings_InstanceId", + table: "DreamMakerSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_InstanceUsers_InstanceId", + table: "InstanceUsers", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_InstanceUsers_UserId", + table: "InstanceUsers", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_InstanceId", + table: "Jobs", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_StartedById", + table: "Jobs", + column: "StartedById"); + + migrationBuilder.CreateIndex( + name: "IX_RepositorySettings_InstanceId", + table: "RepositorySettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RepositorySettings_RevisionInformationId", + table: "RepositorySettings", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_RevisionInformations_Revision", + table: "RevisionInformations", + column: "Revision", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_InstanceId", + table: "TestMerges", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_MergedById", + table: "TestMerges", + column: "MergedById"); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_RevisionInformationId", + table: "TestMerges", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_SystemIdentifier", + table: "Users", + column: "SystemIdentifier", + unique: true, + filter: "[SystemIdentifier] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ChatChannels"); + + migrationBuilder.DropTable( + name: "DreamDaemonSettings"); + + migrationBuilder.DropTable( + name: "DreamMakerSettings"); + + migrationBuilder.DropTable( + name: "InstanceUsers"); + + migrationBuilder.DropTable( + name: "Jobs"); + + migrationBuilder.DropTable( + name: "Logs"); + + migrationBuilder.DropTable( + name: "RepositorySettings"); + + migrationBuilder.DropTable( + name: "ServerSettings"); + + migrationBuilder.DropTable( + name: "TestMerges"); + + migrationBuilder.DropTable( + name: "ChatSettings"); + + migrationBuilder.DropTable( + name: "CompileJobs"); + + migrationBuilder.DropTable( + name: "Instances"); + + migrationBuilder.DropTable( + name: "RevisionInformations"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/SqlServerDatabaseContextModelSnapshot.cs new file mode 100644 index 0000000000..46332b16f3 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/SqlServerDatabaseContextModelSnapshot.cs @@ -0,0 +1,511 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Models.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerDatabaseContext))] + partial class SqlServerDatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.0-preview2-30571") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("ChatSettingsId1"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId"); + + b.HasIndex("ChatSettingsId1"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DiscordBotToken"); + + b.Property("DiscordEnabled"); + + b.Property("InstanceId"); + + b.Property("IrcEnabled"); + + b.Property("IrcHost") + .IsRequired(); + + b.Property("IrcNickServPassword"); + + b.Property("IrcPort"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ExitCode"); + + b.Property("FinishedAt"); + + b.Property("InstanceId"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.Property("StartedAt"); + + b.Property("TargetedPrimaryDirectory"); + + b.Property("TriggeredById"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TriggeredById"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient"); + + b.Property("AutoStart"); + + b.Property("CompileJobId"); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart"); + + b.Property("SoftShutdown"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoCompileInterval"); + + b.Property("CompileJobId"); + + b.Property("InstanceId"); + + b.Property("TargetDme"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConfigurationAllowed"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online"); + + b.Property("Path") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatSettingsRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("RepositoryRights"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId"); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Cancelled"); + + b.Property("Description") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("StartedAt"); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdateInterval"); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("Origin"); + + b.Property("PushTestMergeCommits"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("OriginRevision") + .IsRequired() + .HasMaxLength(40); + + b.Property("Revision") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("Revision") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ServerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EnableTelemetry"); + + b.Property("SystemAuthenticationGroup"); + + b.Property("UpstreamRepository"); + + b.HasKey("Id"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("RevisionInformationId"); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("MergedById"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CreatedAt"); + + b.Property("InstanceManagerRights"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.Property("TokenSecret") + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("SystemIdentifier") + .IsUnique() + .HasFilter("[SystemIdentifier] IS NOT NULL"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ZNetCS.AspNetCore.Logging.EntityFrameworkCore.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EventId"); + + b.Property("Level"); + + b.Property("Message"); + + b.Property("Name") + .HasMaxLength(255); + + b.Property("TimeStamp"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatSettings") + .WithMany("AdminChannels") + .HasForeignKey("ChatSettingsId"); + + b.HasOne("Tgstation.Server.Host.Models.ChatSettings") + .WithMany("GeneralChannels") + .HasForeignKey("ChatSettingsId1"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("ChatSettings") + .HasForeignKey("Tgstation.Server.Host.Models.ChatSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("CompileJobs") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany() + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "TriggeredBy") + .WithMany() + .HasForeignKey("TriggeredById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany() + .HasForeignKey("RevisionInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("TestMerges") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany() + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation") + .WithMany("TestMerges") + .HasForeignKey("RevisionInformationId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs new file mode 100644 index 0000000000..2b6f15c7cb --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Logging; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + sealed class SqlServerDesignTimeDbContextFactory : IDesignTimeDbContextFactory + { + /// + public SqlServerDatabaseContext CreateDbContext(string[] args) => new SqlServerDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.Designer.cs new file mode 100644 index 0000000000..0194884160 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.Designer.cs @@ -0,0 +1,510 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Models.Migrations.Sqlite +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20180410204120_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.0-preview2-30571"); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("ChatSettingsId1"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId"); + + b.HasIndex("ChatSettingsId1"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DiscordBotToken"); + + b.Property("DiscordEnabled"); + + b.Property("InstanceId"); + + b.Property("IrcEnabled"); + + b.Property("IrcHost") + .IsRequired(); + + b.Property("IrcNickServPassword"); + + b.Property("IrcPort"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ExitCode"); + + b.Property("FinishedAt"); + + b.Property("InstanceId"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.Property("StartedAt"); + + b.Property("TargetedPrimaryDirectory"); + + b.Property("TriggeredById"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TriggeredById"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient"); + + b.Property("AutoStart"); + + b.Property("CompileJobId"); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart"); + + b.Property("SoftShutdown"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoCompileInterval"); + + b.Property("CompileJobId"); + + b.Property("InstanceId"); + + b.Property("TargetDme"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConfigurationAllowed"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online"); + + b.Property("Path") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatSettingsRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("RepositoryRights"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId"); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Cancelled"); + + b.Property("Description") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("StartedAt"); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdateInterval"); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("Origin"); + + b.Property("PushTestMergeCommits"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("OriginRevision") + .IsRequired() + .HasMaxLength(40); + + b.Property("Revision") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("Revision") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ServerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EnableTelemetry"); + + b.Property("SystemAuthenticationGroup"); + + b.Property("UpstreamRepository"); + + b.HasKey("Id"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("RevisionInformationId"); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("MergedById"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CreatedAt"); + + b.Property("InstanceManagerRights"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.Property("TokenSecret") + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ZNetCS.AspNetCore.Logging.EntityFrameworkCore.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EventId"); + + b.Property("Level"); + + b.Property("Message"); + + b.Property("Name") + .HasMaxLength(255); + + b.Property("TimeStamp"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatSettings") + .WithMany("AdminChannels") + .HasForeignKey("ChatSettingsId"); + + b.HasOne("Tgstation.Server.Host.Models.ChatSettings") + .WithMany("GeneralChannels") + .HasForeignKey("ChatSettingsId1"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("ChatSettings") + .HasForeignKey("Tgstation.Server.Host.Models.ChatSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("CompileJobs") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany() + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "TriggeredBy") + .WithMany() + .HasForeignKey("TriggeredById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany() + .HasForeignKey("RevisionInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("TestMerges") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany() + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation") + .WithMany("TestMerges") + .HasForeignKey("RevisionInformationId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.cs new file mode 100644 index 0000000000..433fd2978e --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.cs @@ -0,0 +1,535 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations.Sqlite +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Instances", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(nullable: false), + Path = table.Column(nullable: false), + Online = table.Column(nullable: false), + ConfigurationAllowed = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Instances", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Logs", + columns: table => new + { + EventId = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Level = table.Column(nullable: false), + Message = table.Column(nullable: true), + Name = table.Column(maxLength: 255, nullable: true), + TimeStamp = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Logs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RevisionInformations", + columns: table => new + { + Revision = table.Column(maxLength: 40, nullable: false), + OriginRevision = table.Column(maxLength: 40, nullable: false), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true) + }, + constraints: table => + { + table.PrimaryKey("PK_RevisionInformations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ServerSettings", + columns: table => new + { + SystemAuthenticationGroup = table.Column(nullable: true), + EnableTelemetry = table.Column(nullable: false), + UpstreamRepository = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true) + }, + constraints: table => + { + table.PrimaryKey("PK_ServerSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + AdministrationRights = table.Column(nullable: false), + CreatedAt = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + InstanceManagerRights = table.Column(nullable: false), + SystemIdentifier = table.Column(nullable: true), + Name = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + TokenSecret = table.Column(maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ChatSettings", + columns: table => new + { + IrcEnabled = table.Column(nullable: false), + IrcHost = table.Column(nullable: false), + IrcPort = table.Column(nullable: false), + IrcNickServPassword = table.Column(nullable: true), + DiscordEnabled = table.Column(nullable: false), + DiscordBotToken = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatSettings", x => x.Id); + table.ForeignKey( + name: "FK_ChatSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RepositorySettings", + columns: table => new + { + Origin = table.Column(nullable: true), + CommitterName = table.Column(nullable: false), + CommitterEmail = table.Column(nullable: false), + AccessUser = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + PushTestMergeCommits = table.Column(nullable: false), + AutoUpdateInterval = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + InstanceId = table.Column(nullable: false), + RevisionInformationId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RepositorySettings", x => x.Id); + table.ForeignKey( + name: "FK_RepositorySettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RepositorySettings_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CompileJobs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + StartedAt = table.Column(nullable: false), + FinishedAt = table.Column(nullable: false), + TargetedPrimaryDirectory = table.Column(nullable: true), + Output = table.Column(nullable: true), + ExitCode = table.Column(nullable: true), + TriggeredById = table.Column(nullable: false), + RevisionInformationId = table.Column(nullable: false), + InstanceId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CompileJobs", x => x.Id); + table.ForeignKey( + name: "FK_CompileJobs_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CompileJobs_Users_TriggeredById", + column: x => x.TriggeredById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "InstanceUsers", + columns: table => new + { + ByondRights = table.Column(nullable: false), + DreamDaemonRights = table.Column(nullable: false), + DreamMakerRights = table.Column(nullable: false), + RepositoryRights = table.Column(nullable: false), + ChatSettingsRights = table.Column(nullable: false), + ConfigurationRights = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + InstanceId = table.Column(nullable: true), + UserId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_InstanceUsers", x => x.Id); + table.ForeignKey( + name: "FK_InstanceUsers_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_InstanceUsers_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Jobs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Description = table.Column(nullable: false), + StartedAt = table.Column(nullable: false), + StoppedAt = table.Column(nullable: false), + Cancelled = table.Column(nullable: false), + StartedById = table.Column(nullable: false), + InstanceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Jobs", x => x.Id); + table.ForeignKey( + name: "FK_Jobs_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Jobs_Users_StartedById", + column: x => x.StartedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "TestMerges", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + MergedAt = table.Column(nullable: false), + TitleAtMerge = table.Column(nullable: false), + BodyAtMerge = table.Column(nullable: false), + Author = table.Column(nullable: false), + MergedById = table.Column(nullable: false), + InstanceId = table.Column(nullable: true), + RevisionInformationId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TestMerges", x => x.Id); + table.ForeignKey( + name: "FK_TestMerges_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TestMerges_Users_MergedById", + column: x => x.MergedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_TestMerges_RevisionInformations_RevisionInformationId", + column: x => x.RevisionInformationId, + principalTable: "RevisionInformations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ChatChannels", + columns: table => new + { + IrcChannel = table.Column(nullable: true), + DiscordChannelId = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ChatSettingsId = table.Column(nullable: true), + ChatSettingsId1 = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatChannels", x => x.Id); + table.ForeignKey( + name: "FK_ChatChannels_ChatSettings_ChatSettingsId", + column: x => x.ChatSettingsId, + principalTable: "ChatSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ChatChannels_ChatSettings_ChatSettingsId1", + column: x => x.ChatSettingsId1, + principalTable: "ChatSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "DreamDaemonSettings", + columns: table => new + { + AutoStart = table.Column(nullable: false), + AllowWebClient = table.Column(nullable: false), + SoftRestart = table.Column(nullable: false), + SoftShutdown = table.Column(nullable: false), + SecurityLevel = table.Column(nullable: false), + PrimaryPort = table.Column(nullable: false), + SecondaryPort = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ProcessId = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + InstanceId = table.Column(nullable: false), + CompileJobId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id); + table.ForeignKey( + name: "FK_DreamDaemonSettings_CompileJobs_CompileJobId", + column: x => x.CompileJobId, + principalTable: "CompileJobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_DreamDaemonSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DreamMakerSettings", + columns: table => new + { + AutoCompileInterval = table.Column(nullable: true), + TargetDme = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + InstanceId = table.Column(nullable: false), + CompileJobId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DreamMakerSettings", x => x.Id); + table.ForeignKey( + name: "FK_DreamMakerSettings_CompileJobs_CompileJobId", + column: x => x.CompileJobId, + principalTable: "CompileJobs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_DreamMakerSettings_Instances_InstanceId", + column: x => x.InstanceId, + principalTable: "Instances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ChatChannels_ChatSettingsId", + table: "ChatChannels", + column: "ChatSettingsId"); + + migrationBuilder.CreateIndex( + name: "IX_ChatChannels_ChatSettingsId1", + table: "ChatChannels", + column: "ChatSettingsId1"); + + migrationBuilder.CreateIndex( + name: "IX_ChatSettings_InstanceId", + table: "ChatSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_InstanceId", + table: "CompileJobs", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_RevisionInformationId", + table: "CompileJobs", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_CompileJobs_TriggeredById", + table: "CompileJobs", + column: "TriggeredById"); + + migrationBuilder.CreateIndex( + name: "IX_DreamDaemonSettings_CompileJobId", + table: "DreamDaemonSettings", + column: "CompileJobId"); + + migrationBuilder.CreateIndex( + name: "IX_DreamDaemonSettings_InstanceId", + table: "DreamDaemonSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DreamMakerSettings_CompileJobId", + table: "DreamMakerSettings", + column: "CompileJobId"); + + migrationBuilder.CreateIndex( + name: "IX_DreamMakerSettings_InstanceId", + table: "DreamMakerSettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_InstanceUsers_InstanceId", + table: "InstanceUsers", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_InstanceUsers_UserId", + table: "InstanceUsers", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_InstanceId", + table: "Jobs", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_StartedById", + table: "Jobs", + column: "StartedById"); + + migrationBuilder.CreateIndex( + name: "IX_RepositorySettings_InstanceId", + table: "RepositorySettings", + column: "InstanceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RepositorySettings_RevisionInformationId", + table: "RepositorySettings", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_RevisionInformations_Revision", + table: "RevisionInformations", + column: "Revision", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_InstanceId", + table: "TestMerges", + column: "InstanceId"); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_MergedById", + table: "TestMerges", + column: "MergedById"); + + migrationBuilder.CreateIndex( + name: "IX_TestMerges_RevisionInformationId", + table: "TestMerges", + column: "RevisionInformationId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_SystemIdentifier", + table: "Users", + column: "SystemIdentifier", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ChatChannels"); + + migrationBuilder.DropTable( + name: "DreamDaemonSettings"); + + migrationBuilder.DropTable( + name: "DreamMakerSettings"); + + migrationBuilder.DropTable( + name: "InstanceUsers"); + + migrationBuilder.DropTable( + name: "Jobs"); + + migrationBuilder.DropTable( + name: "Logs"); + + migrationBuilder.DropTable( + name: "RepositorySettings"); + + migrationBuilder.DropTable( + name: "ServerSettings"); + + migrationBuilder.DropTable( + name: "TestMerges"); + + migrationBuilder.DropTable( + name: "ChatSettings"); + + migrationBuilder.DropTable( + name: "CompileJobs"); + + migrationBuilder.DropTable( + name: "Instances"); + + migrationBuilder.DropTable( + name: "RevisionInformations"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs new file mode 100644 index 0000000000..499518c925 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs @@ -0,0 +1,509 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Models.Migrations.Sqlite +{ + [DbContext(typeof(SqliteDatabaseContext))] + partial class SqliteDatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.0-preview2-30571"); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("ChatSettingsId1"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId"); + + b.HasIndex("ChatSettingsId1"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DiscordBotToken"); + + b.Property("DiscordEnabled"); + + b.Property("InstanceId"); + + b.Property("IrcEnabled"); + + b.Property("IrcHost") + .IsRequired(); + + b.Property("IrcNickServPassword"); + + b.Property("IrcPort"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ExitCode"); + + b.Property("FinishedAt"); + + b.Property("InstanceId"); + + b.Property("Output"); + + b.Property("RevisionInformationId"); + + b.Property("StartedAt"); + + b.Property("TargetedPrimaryDirectory"); + + b.Property("TriggeredById"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TriggeredById"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient"); + + b.Property("AutoStart"); + + b.Property("CompileJobId"); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart"); + + b.Property("SoftShutdown"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoCompileInterval"); + + b.Property("CompileJobId"); + + b.Property("InstanceId"); + + b.Property("TargetDme"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConfigurationAllowed"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online"); + + b.Property("Path") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatSettingsRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("RepositoryRights"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("UserId"); + + b.ToTable("InstanceUsers"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Cancelled"); + + b.Property("Description") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("StartedAt"); + + b.Property("StartedById"); + + b.Property("StoppedAt"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdateInterval"); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("Origin"); + + b.Property("PushTestMergeCommits"); + + b.Property("RevisionInformationId"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("OriginRevision") + .IsRequired() + .HasMaxLength(40); + + b.Property("Revision") + .IsRequired() + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("Revision") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ServerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EnableTelemetry"); + + b.Property("SystemAuthenticationGroup"); + + b.Property("UpstreamRepository"); + + b.HasKey("Id"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("RevisionInformationId"); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("MergedById"); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CreatedAt"); + + b.Property("InstanceManagerRights"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.Property("TokenSecret") + .HasMaxLength(40); + + b.HasKey("Id"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ZNetCS.AspNetCore.Logging.EntityFrameworkCore.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EventId"); + + b.Property("Level"); + + b.Property("Message"); + + b.Property("Name") + .HasMaxLength(255); + + b.Property("TimeStamp"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatSettings") + .WithMany("AdminChannels") + .HasForeignKey("ChatSettingsId"); + + b.HasOne("Tgstation.Server.Host.Models.ChatSettings") + .WithMany("GeneralChannels") + .HasForeignKey("ChatSettingsId1"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("ChatSettings") + .HasForeignKey("Tgstation.Server.Host.Models.ChatSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("CompileJobs") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany() + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "TriggeredBy") + .WithMany() + .HasForeignKey("TriggeredById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("InstanceUsers") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.User") + .WithMany("InstanceUsers") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany() + .HasForeignKey("RevisionInformationId"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance") + .WithMany("TestMerges") + .HasForeignKey("InstanceId"); + + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany() + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation") + .WithMany("TestMerges") + .HasForeignKey("RevisionInformationId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs new file mode 100644 index 0000000000..21087ac164 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Logging; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + sealed class SqliteDesignTimeDbContextFactory : IDesignTimeDbContextFactory + { + /// + public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); + } +} diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs new file mode 100644 index 0000000000..c46d26c1ba --- /dev/null +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Tgstation.Server.Host.Configuration; + +namespace Tgstation.Server.Host.Models +{ + /// + /// for MySQL + /// + sealed class MySqlDatabaseContext : DatabaseContext + { + /// + /// Construct a + /// + /// The for the + /// The of for the + /// The for the + /// The for the + public MySqlDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) + { } + + /// + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + base.OnConfiguring(options); + options.UseMySQL(ConnectionString); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs new file mode 100644 index 0000000000..7003c01085 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Tgstation.Server.Host.Configuration; + +namespace Tgstation.Server.Host.Models +{ + /// + /// for Sqlserver + /// + sealed class SqlServerDatabaseContext : DatabaseContext + { + /// + /// Construct a + /// + /// The for the + /// The of for the + /// The for the + /// The for the + public SqlServerDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) + { } + + /// + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + base.OnConfiguring(options); + options.UseSqlServer(ConnectionString); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs new file mode 100644 index 0000000000..3581d3d130 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Tgstation.Server.Host.Configuration; + +namespace Tgstation.Server.Host.Models +{ + /// + /// for Sqlite + /// + sealed class SqliteDatabaseContext : DatabaseContext + { + /// + /// Construct a + /// + /// The for the + /// The of for the + /// The for the + /// The for the + public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) + { } + + /// + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + base.OnConfiguring(options); + options.UseSqlite(ConnectionString); + } + } +} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index f97f3f42ea..acd5be3e36 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -41,13 +41,19 @@ + - + + + + + + diff --git a/src/Tgstation.Server.Host/appsettings.Development.json b/src/Tgstation.Server.Host/appsettings.Development.json deleted file mode 100644 index 1350bf6fde..0000000000 --- a/src/Tgstation.Server.Host/appsettings.Development.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Debug" - } - }, - "Console": { - "LogLevel": { - "Default": "Trace" - } - }, - "EntityFramework": { - "LogLevel": { - "Default": "Warning" - } - } - }, - "Database": { - "DatabaseType": "Sqlite", - "ConnectionString": "Data Source=TestDB.sqlite3" - } -} From 18ceaf57d84a91b078329a86b15cab5fe98995f5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 11 Apr 2018 11:25:58 -0400 Subject: [PATCH 077/397] Cleanup ApiHeaders --- src/Tgstation.Server.Api/ApiHeaders.cs | 231 ++++++++++++------ .../Tgstation.Server.Api.csproj | 1 + 2 files changed, 162 insertions(+), 70 deletions(-) diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 894aaa4650..9e7563e3cb 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -1,56 +1,81 @@ -using System; -using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using System; +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 { + /// + /// Represents the header that must be present for every server request + /// public sealed class ApiHeaders { - const string userAgentHeader = "User-Agent"; - const string apiVersionHeader = "Api-Version"; - const string tokenHeader = "Token"; + /// + /// The username header key + /// const string usernameHeader = "Username"; - const string passwordHeader = "Password"; - public static readonly Version CurrentApiVersion = Assembly.GetExecutingAssembly().GetName().Version; + /// + /// The JWT authentication header scheme + /// + const string jwtAuthenticationScheme = "Bearer"; - public IReadOnlyDictionary HeaderEntries => headerEntries; + /// + /// The password authentication header scheme + /// + const string passwordAuthenticationScheme = "Password"; - public string UserAgent { - get => headerEntries[userAgentHeader]; - private set => headerEntries[userAgentHeader] = value; - } + /// + /// TODO: Remove this when https://github.com/dotnet/corefx/pull/26701 makes it into the sdk + /// + const string applicationJson = "application/json"; - public Version ApiVersion - { - get => new Version(headerEntries[apiVersionHeader]); - private set => headerEntries[userAgentHeader] = value.ToString(); - } + /// + /// The current + /// + static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName(); - public string Token - { - get => headerEntries[tokenHeader]; - private set => headerEntries[tokenHeader] = value; - } + /// + /// The client's user agent + /// + public ProductHeaderValue UserAgent { get; } - public string Username - { - get => headerEntries[usernameHeader]; - private set => headerEntries[usernameHeader] = value; - } + /// + /// The client's API version + /// + public Version ApiVersion { get; } - public string Password - { - get => headerEntries[passwordHeader]; - private set => headerEntries[passwordHeader] = value; - } + /// + /// The client's JWT + /// + public string Token { get; } - public bool IsTokenAuthentication => headerEntries.TryGetValue(tokenHeader, out string value); + /// + /// The client's username + /// + public string Username { get; } - readonly Dictionary headerEntries; + /// + /// The client's password + /// + public string Password { get; } - public ApiHeaders(string userAgent, string token) : this(userAgent, token, null, null) + /// + /// If the header uses password or JWT authentication + /// + public bool IsTokenAuthentication => Token != null; + + /// + /// Construct for JWT authentication + /// + /// The value of + /// The value of + public ApiHeaders(ProductHeaderValue userAgent, string token) : this(userAgent, token, null, null) { if (userAgent == null) throw new ArgumentNullException(nameof(userAgent)); @@ -58,7 +83,13 @@ namespace Tgstation.Server.Api throw new ArgumentNullException(nameof(token)); } - public ApiHeaders(string userAgent, string username, string password) : this(userAgent, null, username, password) + /// + /// Construct for password authentication + /// + /// The value of + /// The value of + /// The value of + public ApiHeaders(ProductHeaderValue userAgent, string username, string password) : this(userAgent, null, username, password) { if (userAgent == null) throw new ArgumentNullException(nameof(userAgent)); @@ -68,42 +99,102 @@ namespace Tgstation.Server.Api throw new ArgumentNullException(nameof(password)); } - public ApiHeaders(IReadOnlyDictionary headerEntries) + /// + /// Construct and validates from a + /// + /// The containing the + public ApiHeaders(RequestHeaders requestHeaders) { - this.headerEntries = headerEntries?.ToDictionary(x => x.Key, x => x.Value) ?? throw new ArgumentNullException(nameof(headerEntries)); - AssertHeader(userAgentHeader); - AssertHeader(apiVersionHeader); - try - { - AssertHeader(usernameHeader); - AssertHeader(passwordHeader); - } - catch (InvalidOperationException) - { - AssertHeader(tokenHeader); - } - } + var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(applicationJson); + if (!requestHeaders.Accept.Any(x => x == jsonAccept)) + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", applicationJson)); - void AssertHeader(string headerName) - { - try - { - var headerValue = headerEntries[headerName]; - } - catch(Exception e) - { - throw new InvalidOperationException("Missing required header!", e); - } - } + if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out StringValues userAgentValues) || !ProductInfoHeaderValue.TryParse(userAgentValues.FirstOrDefault(), out ProductInfoHeaderValue clientUserAgent)) + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} headers!", HeaderNames.UserAgent)); - ApiHeaders(string userAgent, string token, string username, string password) - { - headerEntries = new Dictionary(); - ApiVersion = CurrentApiVersion; - UserAgent = userAgent; - Token = token; - Username = username; - Password = password; - } + //assure the client user agent has a name and version + if (String.IsNullOrWhiteSpace(clientUserAgent.Product.Name) || !Version.TryParse(clientUserAgent.Product.Version, out Version clientVersion)) + throw new InvalidOperationException("Malformed client user agent!"); + + var apiUserAgentHeader = userAgentValues[1]; + //make sure the api header matches ours + if (!ProductInfoHeaderValue.TryParse(apiUserAgentHeader, out ProductInfoHeaderValue apiUserAgent) || apiUserAgent.Product.Name != assemblyName.Name) + throw new InvalidOperationException("Missing API user agent!"); + + if (!Version.TryParse(apiUserAgent.Product.Version, out Version ApiVersion)) + throw new InvalidOperationException("Malformed API version!"); + + //check api version compatibility + var ourVersion = assemblyName.Version; + if (ourVersion.Major != ApiVersion.Major || ourVersion.Minor != ApiVersion.Minor || ourVersion.Build > ApiVersion.Build) + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Given API version is incompatible with version {0}!", ourVersion)); + + if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization)) + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization)); + var scheme = authorization.First(); + + if (String.IsNullOrWhiteSpace(scheme)) + throw new InvalidOperationException("Missing authentication scheme!"); + var parameter = authorization[1]; + if (String.IsNullOrEmpty(parameter)) + throw new InvalidOperationException("Missing authentication parameter!"); + + switch (scheme) + { + case jwtAuthenticationScheme: + Token = parameter; + break; + case passwordAuthenticationScheme: + Password = parameter; + var fail = !requestHeaders.Headers.TryGetValue(usernameHeader, out StringValues values); + if (!fail) + { + Username = values.FirstOrDefault(); + fail = String.IsNullOrWhiteSpace(Username); + } + if (fail) + throw new InvalidOperationException("Missing Username header!"); + break; + default: + throw new InvalidOperationException("Invalid authentication scheme!"); + } + } + + /// + /// Construct + /// + /// The value of + /// The value of + /// The value of + /// The value of + ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password) + { + UserAgent = userAgent; + Token = token; + Username = username; + Password = password; + } + + /// + /// Set using the . This initially clears + /// + /// The to set + public void SetRequestHeaders(HttpRequestHeaders headers) + { + if (headers == null) + throw new ArgumentNullException(nameof(headers)); + + headers.Clear(); + headers.Accept.Add(new MediaTypeWithQualityHeaderValue(applicationJson)); + if (IsTokenAuthentication) + headers.Authorization = new AuthenticationHeaderValue(jwtAuthenticationScheme, Token); + else + { + headers.Authorization = new AuthenticationHeaderValue(passwordAuthenticationScheme, Password); + headers.Add(usernameHeader, Username); + } + headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent)); + headers.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue(assemblyName.Name, assemblyName.Version.ToString()))); + } } } diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 77d9900e91..3da598cb1b 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -19,6 +19,7 @@ + From e62fc5618490c5a2142c72c4af70153bf92fafdf Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 11 Apr 2018 12:39:34 -0400 Subject: [PATCH 078/397] Fix release build, deleted schema for now --- .../Models/Administration.cs | 6 +- src/Tgstation.Server.Api/Models/CompileJob.cs | 1 + .../Models/DreamDaemon.cs | 6 +- .../Models/DreamDaemonSecurity.cs | 2 +- src/Tgstation.Server.Api/Models/DreamMaker.cs | 2 +- .../Models/Internal/CompileJob.cs | 3 + .../Models/Internal/DreamDaemonSettings.cs | 4 + .../Models/Internal/DreamMakerSettings.cs | 4 + .../Models/Internal/Job.cs | 11 +- .../Models/Internal/RepositorySettings.cs | 10 +- .../Models/Internal/RevisionInformation.cs | 11 + .../Models/Internal/ServerSettings.cs | 3 + .../Models/Internal/TestMerge.cs | 20 +- .../Models/Internal/User.cs | 5 +- src/Tgstation.Server.Api/Models/Job.cs | 7 +- src/Tgstation.Server.Api/Models/Repository.cs | 13 +- .../Models/RevisionInformation.cs | 6 +- src/Tgstation.Server.Api/Models/TestMerge.cs | 4 + .../Models/TestMergeParameters.cs | 9 + src/Tgstation.Server.Api/Models/Token.cs | 7 +- src/Tgstation.Server.Api/Models/User.cs | 7 +- .../Rights/AdministrationRights.cs | 4 +- .../Rights/DreamDaemonRights.cs | 16 +- .../Rights/DreamMakerRights.cs | 4 +- .../Rights/RepositoryRights.cs | 10 +- .../Components/IDreamDaemonClient.cs | 2 +- src/Tgstation.Server.Host.Startup/IServer.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 16 - .../Core/AuthenticationContext.cs | 7 +- .../Models/DatabaseContext.cs | 18 +- .../Models/DatabaseSeeder.cs | 2 +- .../DesignTimeDbContextFactoryHelpers.cs | 2 +- .../20180410203936_InitialCreate.Designer.cs | 512 ----------------- .../SqlServer/20180410203936_InitialCreate.cs | 537 ------------------ .../SqlServerDatabaseContextModelSnapshot.cs | 511 ----------------- .../20180410204120_InitialCreate.Designer.cs | 510 ----------------- .../Sqlite/20180410204120_InitialCreate.cs | 535 ----------------- .../SqliteDatabaseContextModelSnapshot.cs | 509 ----------------- .../Models/MySqlDatabaseContext.cs | 10 +- .../Models/SqlServerDatabaseContext.cs | 10 +- .../Models/SqliteDatabaseContext.cs | 10 +- src/Tgstation.Server.Host/Models/User.cs | 5 +- 42 files changed, 171 insertions(+), 3202 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.Designer.cs delete mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.cs delete mode 100644 src/Tgstation.Server.Host/Models/Migrations/SqlServer/SqlServerDatabaseContextModelSnapshot.cs delete mode 100644 src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.Designer.cs delete mode 100644 src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.cs delete mode 100644 src/Tgstation.Server.Host/Models/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 374d82a178..7f04e2d428 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -4,9 +4,7 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { - /// - /// Metadata about an installation - /// + /// public sealed class Administration : Internal.ServerSettings { /// @@ -28,7 +26,7 @@ namespace Tgstation.Server.Api.Models public Version CurrentVersion { get; set; } /// - /// Users in the + /// Users in the /// [Permissions(DenyWrite = true)] public IReadOnlyList Users { get; set; } diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 4478dde763..2cc0f74988 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -1,5 +1,6 @@ namespace Tgstation.Server.Api.Models { + /// public sealed class CompileJob : Internal.CompileJob { /// diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 2c48828bbf..49571a3fa3 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -6,7 +6,6 @@ namespace Tgstation.Server.Api.Models /// /// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server /// - [Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)] public sealed class DreamDaemon : DreamDaemonSettings { /// @@ -21,8 +20,11 @@ namespace Tgstation.Server.Api.Models [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] public DreamDaemonStatus? Status { get; set; } + /// + /// The current of + /// [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] - public DreamDaemonSecurity? CurrentSecurityLevel { get; set; } + public DreamDaemonSecurity? CurrentSecurity { get; set; } /// /// The port the running instance is set to diff --git a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs index fef6a8f20c..094332d1e4 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs @@ -8,7 +8,7 @@ /// /// Server is unrestricted in terms of file access and shell commands /// - Trusted = 0, + Trusted, /// /// Server will not be able to run shell commands or access files outside it's working directory /// diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index 6864a7e4aa..de1d893846 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -6,12 +6,12 @@ namespace Tgstation.Server.Api.Models /// /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile /// - [Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)] public sealed class DreamMaker : DreamMakerSettings { /// /// The last ran /// + [Permissions(DenyWrite = true)] public CompileJob LastJob { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index b9af1662d6..9fccf5e173 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -3,6 +3,9 @@ using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { + /// + /// Represents a run of + /// public class CompileJob { /// diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index 610d6b59a1..6b0520c59e 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -2,6 +2,10 @@ namespace Tgstation.Server.Api.Models.Internal { + /// + /// Configurable settings for + /// + [Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)] public class DreamDaemonSettings { /// diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs index f1c826ec03..8d31ef3ef0 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs @@ -2,6 +2,10 @@ namespace Tgstation.Server.Api.Models.Internal { + /// + /// Configurable settings for + /// + [Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)] public class DreamMakerSettings { /// diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index 0effd5cbfc..a3f38d806d 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -3,33 +3,42 @@ using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { + /// + /// Represents a long running job + /// + [Model] public class Job { /// /// The ID /// + [Permissions(DenyWrite = true)] public long Id { get; set; } /// - /// Human readable description of the + /// English description of the /// + [Permissions(DenyWrite = true)] [Required] public string Description { get; set; } /// /// When the was started /// + [Permissions(DenyWrite = true)] [Required] public DateTimeOffset StartedAt { get; set; } /// /// When the stopped /// + [Permissions(DenyWrite = true)] public DateTimeOffset StoppedAt { get; set; } /// /// If the was cancelled /// + [Permissions(DenyWrite = true)] public bool Cancelled { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index 1d50db143c..11917d2f81 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -3,14 +3,12 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { + /// + /// Represents configurable settings for a + /// + [Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)] public class RepositorySettings { - /// - /// The origin URL. If , the does not exist - /// - [Permissions(WriteRight = RepositoryRights.SetOrigin)] - public string Origin { get; set; } - /// /// The name of the committer /// diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs index 5914d3d962..44eb907a89 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs @@ -2,11 +2,22 @@ namespace Tgstation.Server.Api.Models.Internal { + /// + /// Represents information about a current git revison + /// public class RevisionInformation { + /// + /// The revision sha + /// + [Permissions(DenyWrite = true)] [Required, StringLength(40)] public string Revision { get; set; } + /// + /// The sha of the most recent remote commit + /// + [Permissions(DenyWrite = true)] [Required, StringLength(40)] public string OriginRevision { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs index 9a46e42237..f044801219 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs @@ -2,6 +2,9 @@ namespace Tgstation.Server.Api.Models.Internal { + /// + /// Metadata about an installation + /// [Model(RightsType.Administration)] public class ServerSettings { diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs b/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs index 9db2ffcb83..9e1d04f64d 100644 --- a/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs +++ b/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs @@ -3,19 +3,37 @@ using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { - public class TestMerge + /// + /// Represents a merge of a GitHub pull request + /// + public class TestMerge : TestMergeParameters { + /// + /// The ID of the + /// public long Id { get; set; } + /// + /// When the was created + /// [Required] public DateTimeOffset MergedAt { get; set; } + /// + /// The title of the pull request + /// [Required] public string TitleAtMerge { get; set; } + /// + /// The body of the pull request + /// [Required] public string BodyAtMerge { get; set; } + /// + /// The author of the pull request + /// [Required] public string Author { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Internal/User.cs b/src/Tgstation.Server.Api/Models/Internal/User.cs index 0368a0d1b9..2a62da30b2 100644 --- a/src/Tgstation.Server.Api/Models/Internal/User.cs +++ b/src/Tgstation.Server.Api/Models/Internal/User.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Represents a server /// - [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers)] + [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers, CanCrud = true)] public class User { /// @@ -16,6 +16,9 @@ namespace Tgstation.Server.Api.Models.Internal [Permissions(DenyWrite = true)] public long Id { get; set; } + /// + /// When the was created + /// [Permissions(DenyWrite = true)] [Required] public DateTimeOffset CreatedAt { get; set; } diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs index 959d546962..ebbf23bb21 100644 --- a/src/Tgstation.Server.Api/Models/Job.cs +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -3,19 +3,24 @@ /// /// Represents a long running job on the server. Model is read-only, updates attempt to cancel the job /// - [Model] public sealed class Job : Internal.Job { /// /// If the has incremental progress, this will range from 1 - 100. 0 otherwise /// + [Permissions(DenyWrite = true)] public int Progress { get; set; } /// /// If the current user has permission to cancel the job /// + [Permissions(DenyWrite = true)] public bool UserCanCancel { get; set; } + /// + /// The that started the job + /// + [Permissions(DenyWrite = true)] public User StartedBy { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index fbc279d226..96ca42c58c 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -6,15 +6,23 @@ namespace Tgstation.Server.Api.Models /// /// Represents a git repository /// - [Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)] public sealed class Repository : Internal.RepositorySettings { + /// + /// The origin URL. If , the does not exist + /// + [Permissions(WriteRight = RepositoryRights.SetOrigin)] + public string Origin { get; set; } + /// /// The commit HEAD points to /// [Permissions(WriteRight = RepositoryRights.SetSha)] public string NewRevision { get; set; } + /// + /// The current for the + /// [Permissions(DenyWrite = true)] public RevisionInformation RevisionInformation { get; set; } @@ -24,6 +32,9 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = RepositoryRights.SetReference)] public string Reference { get; set; } + /// + /// for new s + /// [Permissions(WriteRight = RepositoryRights.MergePullRequest)] public List NewTestMerges { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs index 14f5fffa0a..0c769f02e7 100644 --- a/src/Tgstation.Server.Api/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs @@ -2,8 +2,12 @@ namespace Tgstation.Server.Api.Models { + /// public sealed class RevisionInformation : Internal.RevisionInformation - { + { + /// + /// The s active in the + /// public List TestMerges { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/TestMerge.cs b/src/Tgstation.Server.Api/Models/TestMerge.cs index 706b0b58a8..d448504e2d 100644 --- a/src/Tgstation.Server.Api/Models/TestMerge.cs +++ b/src/Tgstation.Server.Api/Models/TestMerge.cs @@ -1,7 +1,11 @@ namespace Tgstation.Server.Api.Models { + /// public sealed class TestMerge : Internal.TestMerge { + /// + /// The who created the + /// public User MergedBy { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs index 06d5f7ebaf..2c3ba33f02 100644 --- a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs +++ b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs @@ -2,10 +2,19 @@ namespace Tgstation.Server.Api.Models { + /// + /// Parameters for creating a + /// public class TestMergeParameters { + /// + /// The number of the pull request + /// public int Number { get; set; } + /// + /// The sha of the pull request revision to merge + /// [Required] public string PullRequestRevision { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index 6e806cf9a6..5f75d2ff03 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,8 +1,13 @@ namespace Tgstation.Server.Api.Models { - /// + /// + /// Represents a JWT returned by the API + /// public sealed class Token { + /// + /// The value of the JWT + /// public string Value { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index bb8c604138..78bdb3e934 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -1,10 +1,11 @@ namespace Tgstation.Server.Api.Models { - /// - /// Represents a server - /// + /// public sealed class User : Internal.User { + /// + /// If the is enabled since users cannot be deleted + /// [Permissions(DenyWrite = true)] public bool Enabled { get; set; } } diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index c27ae958c9..bc7d302397 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Api.Rights /// ChangeAuthenticationGroup = 1, /// - /// User can change + /// User can change /// ChangeTelemetry = 2, /// @@ -33,7 +33,7 @@ namespace Tgstation.Server.Api.Rights /// ChangeVersion = 16, /// - /// User can change + /// User can change /// SetUpstreamRepository = 32 } diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index 51591eb334..f99f63cd58 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Api.Rights /// None = 0, /// - /// User can read , , and + /// User can read /// ReadRevision = 1, /// @@ -21,27 +21,27 @@ namespace Tgstation.Server.Api.Rights /// SetPorts = 2, /// - /// User can change + /// User can change /// SetAutoStart = 4, /// - /// User set + /// User set /// SetSecurity = 8, /// - /// User can read all ports, , , , , and + /// User can read all ports, , , , , and /// ReadMetadata = 16, /// - /// User can change + /// User can change /// SetWebClient = 32, /// - /// User can enable + /// User can enable /// SoftRestart = 64, /// - /// User can enable + /// User can enable /// SoftShutdown = 128, /// @@ -53,7 +53,7 @@ namespace Tgstation.Server.Api.Rights /// Shutdown = 512, /// - /// User can start and disable and + /// User can start and disable and /// Start = 1024 } diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index f770f8c01e..809816dbf9 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -25,11 +25,11 @@ namespace Tgstation.Server.Api.Rights /// CancelCompile = 4, /// - /// User may modify + /// User may modify /// SetAutoCompile = 8, /// - /// User may modify + /// User may modify /// SetDme = 16 } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index 80339d9d7f..c418507036 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -29,19 +29,19 @@ namespace Tgstation.Server.Api.Rights /// UpdateBranch = 16, /// - /// User may change and + /// User may change and /// ChangeCommitter = 32, /// - /// User may change + /// User may change /// ChangeTestMergeCommits = 64, /// - /// User may change + /// User may change /// ChangeAutoUpdate = 128, /// - /// User may read and change and + /// User may read and change and /// ChangeCredentials = 256, /// @@ -49,7 +49,7 @@ namespace Tgstation.Server.Api.Rights /// SetReference = 512, /// - /// User may read all fields in the with the exception of + /// User may read all fields in the with the exception of /// Read = 1024, } diff --git a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs index 228055c9c2..1947c4170d 100644 --- a/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs +++ b/src/Tgstation.Server.Client/Components/IDreamDaemonClient.cs @@ -32,7 +32,7 @@ namespace Tgstation.Server.Client.Components Task Shutdown(CancellationToken cancellationToken); /// - /// Update . This may trigger + /// Update . This may trigger /// /// The to update /// The for the operation diff --git a/src/Tgstation.Server.Host.Startup/IServer.cs b/src/Tgstation.Server.Host.Startup/IServer.cs index 28a2428211..299e193036 100644 --- a/src/Tgstation.Server.Host.Startup/IServer.cs +++ b/src/Tgstation.Server.Host.Startup/IServer.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Startup public interface IServer { /// - /// The path to the updated assembly to run if any. Populated once returns + /// The path to the updated assembly to run if any. Populated once returns /// string UpdatePath { get; } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b70e80286b..9f8f436f50 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -55,7 +55,6 @@ namespace Tgstation.Server.Host.Core services.AddMvc(); services.AddOptions(); - services.AddLocalization(); var databaseConfiguration = databaseConfigurationSection.Get(); void ConfigureDatabase(DbContextOptionsBuilder builder) @@ -99,21 +98,6 @@ namespace Tgstation.Server.Host.Core if (hostingEnvironment.IsDevelopment()) applicationBuilder.UseDeveloperExceptionPage(); - var defaultCulture = new CultureInfo("en"); - var supportedCultures = new List - { - defaultCulture - }; - - CultureInfo.CurrentCulture = defaultCulture; - CultureInfo.CurrentUICulture = defaultCulture; - - applicationBuilder.UseRequestLocalization(new RequestLocalizationOptions - { - SupportedCultures = supportedCultures, - SupportedUICultures = supportedCultures, - }); - applicationBuilder.UseAsyncInitialization(async (cancellationToken) => { using (var scop = applicationBuilder.ApplicationServices.CreateScope()) diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs index aa8235f1b8..9ba9cd4867 100644 --- a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using System; using System.Net; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -39,7 +38,7 @@ namespace Tgstation.Server.Host.Core ApiHeaders headers; try { - headers = new ApiHeaders(httpContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value.First())); + headers = new ApiHeaders(httpContext.Request.GetTypedHeaders()); } catch (InvalidOperationException) { @@ -87,12 +86,12 @@ namespace Tgstation.Server.Host.Core /// /// Construct a /// - /// The value of + /// The value of /// The value of public AuthenticationContext(ISystemIdentity systemIdentity, IDatabaseContext databaseContext) { - SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity)); this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + SystemIdentity = systemIdentity; } /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 9651b6b2db..a429bf65eb 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -38,27 +38,27 @@ namespace Tgstation.Server.Host.Models public DbSet RepositorySettings { get; set; } /// - /// The connection string for the + /// The connection string for the /// protected string ConnectionString => databaseConfiguration.ConnectionString; /// - /// The for the + /// The for the /// readonly DatabaseConfiguration databaseConfiguration; /// - /// The for the + /// The for the /// readonly ILoggerFactory loggerFactory; /// - /// The for the + /// The for the /// readonly IDatabaseSeeder databaseSeeder; /// - /// Construct a + /// Construct a /// - /// The for the + /// The for the /// The containing the value of /// The value of /// The value of @@ -79,7 +79,7 @@ namespace Tgstation.Server.Host.Models modelBuilder.Entity().ToTable(nameof(Logs)); modelBuilder.Entity().HasIndex(x => x.Revision).IsUnique(); - modelBuilder.Entity().HasIndex(x => x.SystemIdentifier).IsUnique(); + modelBuilder.Entity().HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique(); } /// @@ -105,9 +105,13 @@ namespace Tgstation.Server.Host.Models /// public async Task Initialize(CancellationToken cancellationToken) { +#if DEBUG + await Database.EnsureCreatedAsync().ConfigureAwait(false); +#else var migrations = await Database.GetAppliedMigrationsAsync().ConfigureAwait(false); var wasEmpty = !migrations.Any(); await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); +#endif if (wasEmpty) await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index 278692554e..6a2cd85c0d 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Models const string DefaultUpstreamRepository = "https://github.com/tgstation/tgstation-server"; /// - /// The for the + /// The for the /// readonly ICryptographySuite cryptographySuite; diff --git a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs index 9c1da8c24e..c633cc1ec4 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs @@ -7,7 +7,7 @@ using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Models.Migrations { /// - /// Contains helpers for creating design time s + /// Contains helpers for creating design time s /// static class DesignTimeDbContextFactoryHelpers { diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.Designer.cs deleted file mode 100644 index 44d5df743e..0000000000 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.Designer.cs +++ /dev/null @@ -1,512 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Tgstation.Server.Host.Models; - -namespace Tgstation.Server.Host.Models.Migrations.SqlServer -{ - [DbContext(typeof(SqlServerDatabaseContext))] - [Migration("20180410203936_InitialCreate")] - partial class InitialCreate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-preview2-30571") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ChatSettingsId"); - - b.Property("ChatSettingsId1"); - - b.Property("DiscordChannelId"); - - b.Property("IrcChannel"); - - b.HasKey("Id"); - - b.HasIndex("ChatSettingsId"); - - b.HasIndex("ChatSettingsId1"); - - b.ToTable("ChatChannels"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("DiscordBotToken"); - - b.Property("DiscordEnabled"); - - b.Property("InstanceId"); - - b.Property("IrcEnabled"); - - b.Property("IrcHost") - .IsRequired(); - - b.Property("IrcNickServPassword"); - - b.Property("IrcPort"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("ChatSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ExitCode"); - - b.Property("FinishedAt"); - - b.Property("InstanceId"); - - b.Property("Output"); - - b.Property("RevisionInformationId"); - - b.Property("StartedAt"); - - b.Property("TargetedPrimaryDirectory"); - - b.Property("TriggeredById"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("RevisionInformationId"); - - b.HasIndex("TriggeredById"); - - b.ToTable("CompileJobs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessToken"); - - b.Property("AllowWebClient"); - - b.Property("AutoStart"); - - b.Property("CompileJobId"); - - b.Property("InstanceId"); - - b.Property("PrimaryPort"); - - b.Property("ProcessId"); - - b.Property("SecondaryPort"); - - b.Property("SecurityLevel"); - - b.Property("SoftRestart"); - - b.Property("SoftShutdown"); - - b.HasKey("Id"); - - b.HasIndex("CompileJobId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("DreamDaemonSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AutoCompileInterval"); - - b.Property("CompileJobId"); - - b.Property("InstanceId"); - - b.Property("TargetDme"); - - b.HasKey("Id"); - - b.HasIndex("CompileJobId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("DreamMakerSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConfigurationAllowed"); - - b.Property("Name") - .IsRequired(); - - b.Property("Online"); - - b.Property("Path") - .IsRequired(); - - b.HasKey("Id"); - - b.ToTable("Instances"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ByondRights"); - - b.Property("ChatSettingsRights"); - - b.Property("ConfigurationRights"); - - b.Property("DreamDaemonRights"); - - b.Property("DreamMakerRights"); - - b.Property("InstanceId"); - - b.Property("RepositoryRights"); - - b.Property("UserId"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("UserId"); - - b.ToTable("InstanceUsers"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Cancelled"); - - b.Property("Description") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("StartedAt"); - - b.Property("StartedById"); - - b.Property("StoppedAt"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("StartedById"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessToken"); - - b.Property("AccessUser"); - - b.Property("AutoUpdateInterval"); - - b.Property("CommitterEmail") - .IsRequired(); - - b.Property("CommitterName") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("Origin"); - - b.Property("PushTestMergeCommits"); - - b.Property("RevisionInformationId"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.HasIndex("RevisionInformationId"); - - b.ToTable("RepositorySettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("OriginRevision") - .IsRequired() - .HasMaxLength(40); - - b.Property("Revision") - .IsRequired() - .HasMaxLength(40); - - b.HasKey("Id"); - - b.HasIndex("Revision") - .IsUnique(); - - b.ToTable("RevisionInformations"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ServerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("EnableTelemetry"); - - b.Property("SystemAuthenticationGroup"); - - b.Property("UpstreamRepository"); - - b.HasKey("Id"); - - b.ToTable("ServerSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Author") - .IsRequired(); - - b.Property("BodyAtMerge") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("MergedAt"); - - b.Property("MergedById"); - - b.Property("RevisionInformationId"); - - b.Property("TitleAtMerge") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("MergedById"); - - b.HasIndex("RevisionInformationId"); - - b.ToTable("TestMerges"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AdministrationRights"); - - b.Property("CreatedAt"); - - b.Property("InstanceManagerRights"); - - b.Property("Name") - .IsRequired(); - - b.Property("PasswordHash"); - - b.Property("SystemIdentifier"); - - b.Property("TokenSecret") - .HasMaxLength(40); - - b.HasKey("Id"); - - b.HasIndex("SystemIdentifier") - .IsUnique() - .HasFilter("[SystemIdentifier] IS NOT NULL"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("ZNetCS.AspNetCore.Logging.EntityFrameworkCore.Log", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("EventId"); - - b.Property("Level"); - - b.Property("Message"); - - b.Property("Name") - .HasMaxLength(255); - - b.Property("TimeStamp"); - - b.HasKey("Id"); - - b.ToTable("Logs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.HasOne("Tgstation.Server.Host.Models.ChatSettings") - .WithMany("AdminChannels") - .HasForeignKey("ChatSettingsId"); - - b.HasOne("Tgstation.Server.Host.Models.ChatSettings") - .WithMany("GeneralChannels") - .HasForeignKey("ChatSettingsId1"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("ChatSettings") - .HasForeignKey("Tgstation.Server.Host.Models.ChatSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("CompileJobs") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany() - .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "TriggeredBy") - .WithMany() - .HasForeignKey("TriggeredById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamDaemonSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamMakerSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("InstanceUsers") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.User") - .WithMany("InstanceUsers") - .HasForeignKey("UserId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("Jobs") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") - .WithMany() - .HasForeignKey("StartedById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("RepositorySettings") - .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany() - .HasForeignKey("RevisionInformationId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("TestMerges") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") - .WithMany() - .HasForeignKey("MergedById") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation") - .WithMany("TestMerges") - .HasForeignKey("RevisionInformationId"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.cs deleted file mode 100644 index 0a9656c3c1..0000000000 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/20180410203936_InitialCreate.cs +++ /dev/null @@ -1,537 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Tgstation.Server.Host.Models.Migrations.SqlServer -{ - public partial class InitialCreate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Instances", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - Name = table.Column(nullable: false), - Path = table.Column(nullable: false), - Online = table.Column(nullable: false), - ConfigurationAllowed = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Instances", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Logs", - columns: table => new - { - EventId = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - Level = table.Column(nullable: false), - Message = table.Column(nullable: true), - Name = table.Column(maxLength: 255, nullable: true), - TimeStamp = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Logs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "RevisionInformations", - columns: table => new - { - Revision = table.Column(maxLength: 40, nullable: false), - OriginRevision = table.Column(maxLength: 40, nullable: false), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn) - }, - constraints: table => - { - table.PrimaryKey("PK_RevisionInformations", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ServerSettings", - columns: table => new - { - SystemAuthenticationGroup = table.Column(nullable: true), - EnableTelemetry = table.Column(nullable: false), - UpstreamRepository = table.Column(nullable: true), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn) - }, - constraints: table => - { - table.PrimaryKey("PK_ServerSettings", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - AdministrationRights = table.Column(nullable: false), - CreatedAt = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceManagerRights = table.Column(nullable: false), - SystemIdentifier = table.Column(nullable: true), - Name = table.Column(nullable: false), - PasswordHash = table.Column(nullable: true), - TokenSecret = table.Column(maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ChatSettings", - columns: table => new - { - IrcEnabled = table.Column(nullable: false), - IrcHost = table.Column(nullable: false), - IrcPort = table.Column(nullable: false), - IrcNickServPassword = table.Column(nullable: true), - DiscordEnabled = table.Column(nullable: false), - DiscordBotToken = table.Column(nullable: true), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ChatSettings", x => x.Id); - table.ForeignKey( - name: "FK_ChatSettings_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "RepositorySettings", - columns: table => new - { - Origin = table.Column(nullable: true), - CommitterName = table.Column(nullable: false), - CommitterEmail = table.Column(nullable: false), - AccessUser = table.Column(nullable: true), - AccessToken = table.Column(nullable: true), - PushTestMergeCommits = table.Column(nullable: false), - AutoUpdateInterval = table.Column(nullable: true), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false), - RevisionInformationId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_RepositorySettings", x => x.Id); - table.ForeignKey( - name: "FK_RepositorySettings_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_RepositorySettings_RevisionInformations_RevisionInformationId", - column: x => x.RevisionInformationId, - principalTable: "RevisionInformations", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "CompileJobs", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - StartedAt = table.Column(nullable: false), - FinishedAt = table.Column(nullable: false), - TargetedPrimaryDirectory = table.Column(nullable: true), - Output = table.Column(nullable: true), - ExitCode = table.Column(nullable: true), - TriggeredById = table.Column(nullable: false), - RevisionInformationId = table.Column(nullable: false), - InstanceId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_CompileJobs", x => x.Id); - table.ForeignKey( - name: "FK_CompileJobs_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", - column: x => x.RevisionInformationId, - principalTable: "RevisionInformations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_CompileJobs_Users_TriggeredById", - column: x => x.TriggeredById, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "InstanceUsers", - columns: table => new - { - ByondRights = table.Column(nullable: false), - DreamDaemonRights = table.Column(nullable: false), - DreamMakerRights = table.Column(nullable: false), - RepositoryRights = table.Column(nullable: false), - ChatSettingsRights = table.Column(nullable: false), - ConfigurationRights = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: true), - UserId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_InstanceUsers", x => x.Id); - table.ForeignKey( - name: "FK_InstanceUsers_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_InstanceUsers_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Jobs", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - Description = table.Column(nullable: false), - StartedAt = table.Column(nullable: false), - StoppedAt = table.Column(nullable: false), - Cancelled = table.Column(nullable: false), - StartedById = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Jobs", x => x.Id); - table.ForeignKey( - name: "FK_Jobs_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Jobs_Users_StartedById", - column: x => x.StartedById, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "TestMerges", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - MergedAt = table.Column(nullable: false), - TitleAtMerge = table.Column(nullable: false), - BodyAtMerge = table.Column(nullable: false), - Author = table.Column(nullable: false), - MergedById = table.Column(nullable: false), - InstanceId = table.Column(nullable: true), - RevisionInformationId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TestMerges", x => x.Id); - table.ForeignKey( - name: "FK_TestMerges_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_TestMerges_Users_MergedById", - column: x => x.MergedById, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_TestMerges_RevisionInformations_RevisionInformationId", - column: x => x.RevisionInformationId, - principalTable: "RevisionInformations", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "ChatChannels", - columns: table => new - { - IrcChannel = table.Column(nullable: true), - DiscordChannelId = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - ChatSettingsId = table.Column(nullable: true), - ChatSettingsId1 = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_ChatChannels", x => x.Id); - table.ForeignKey( - name: "FK_ChatChannels_ChatSettings_ChatSettingsId", - column: x => x.ChatSettingsId, - principalTable: "ChatSettings", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_ChatChannels_ChatSettings_ChatSettingsId1", - column: x => x.ChatSettingsId1, - principalTable: "ChatSettings", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "DreamDaemonSettings", - columns: table => new - { - AutoStart = table.Column(nullable: false), - AllowWebClient = table.Column(nullable: false), - SoftRestart = table.Column(nullable: false), - SoftShutdown = table.Column(nullable: false), - SecurityLevel = table.Column(nullable: false), - PrimaryPort = table.Column(nullable: false), - SecondaryPort = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - ProcessId = table.Column(nullable: true), - AccessToken = table.Column(nullable: true), - InstanceId = table.Column(nullable: false), - CompileJobId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id); - table.ForeignKey( - name: "FK_DreamDaemonSettings_CompileJobs_CompileJobId", - column: x => x.CompileJobId, - principalTable: "CompileJobs", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_DreamDaemonSettings_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "DreamMakerSettings", - columns: table => new - { - AutoCompileInterval = table.Column(nullable: true), - TargetDme = table.Column(nullable: true), - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - InstanceId = table.Column(nullable: false), - CompileJobId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_DreamMakerSettings", x => x.Id); - table.ForeignKey( - name: "FK_DreamMakerSettings_CompileJobs_CompileJobId", - column: x => x.CompileJobId, - principalTable: "CompileJobs", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_DreamMakerSettings_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_ChatChannels_ChatSettingsId", - table: "ChatChannels", - column: "ChatSettingsId"); - - migrationBuilder.CreateIndex( - name: "IX_ChatChannels_ChatSettingsId1", - table: "ChatChannels", - column: "ChatSettingsId1"); - - migrationBuilder.CreateIndex( - name: "IX_ChatSettings_InstanceId", - table: "ChatSettings", - column: "InstanceId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_CompileJobs_InstanceId", - table: "CompileJobs", - column: "InstanceId"); - - migrationBuilder.CreateIndex( - name: "IX_CompileJobs_RevisionInformationId", - table: "CompileJobs", - column: "RevisionInformationId"); - - migrationBuilder.CreateIndex( - name: "IX_CompileJobs_TriggeredById", - table: "CompileJobs", - column: "TriggeredById"); - - migrationBuilder.CreateIndex( - name: "IX_DreamDaemonSettings_CompileJobId", - table: "DreamDaemonSettings", - column: "CompileJobId"); - - migrationBuilder.CreateIndex( - name: "IX_DreamDaemonSettings_InstanceId", - table: "DreamDaemonSettings", - column: "InstanceId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_DreamMakerSettings_CompileJobId", - table: "DreamMakerSettings", - column: "CompileJobId"); - - migrationBuilder.CreateIndex( - name: "IX_DreamMakerSettings_InstanceId", - table: "DreamMakerSettings", - column: "InstanceId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_InstanceUsers_InstanceId", - table: "InstanceUsers", - column: "InstanceId"); - - migrationBuilder.CreateIndex( - name: "IX_InstanceUsers_UserId", - table: "InstanceUsers", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_Jobs_InstanceId", - table: "Jobs", - column: "InstanceId"); - - migrationBuilder.CreateIndex( - name: "IX_Jobs_StartedById", - table: "Jobs", - column: "StartedById"); - - migrationBuilder.CreateIndex( - name: "IX_RepositorySettings_InstanceId", - table: "RepositorySettings", - column: "InstanceId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_RepositorySettings_RevisionInformationId", - table: "RepositorySettings", - column: "RevisionInformationId"); - - migrationBuilder.CreateIndex( - name: "IX_RevisionInformations_Revision", - table: "RevisionInformations", - column: "Revision", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_TestMerges_InstanceId", - table: "TestMerges", - column: "InstanceId"); - - migrationBuilder.CreateIndex( - name: "IX_TestMerges_MergedById", - table: "TestMerges", - column: "MergedById"); - - migrationBuilder.CreateIndex( - name: "IX_TestMerges_RevisionInformationId", - table: "TestMerges", - column: "RevisionInformationId"); - - migrationBuilder.CreateIndex( - name: "IX_Users_SystemIdentifier", - table: "Users", - column: "SystemIdentifier", - unique: true, - filter: "[SystemIdentifier] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ChatChannels"); - - migrationBuilder.DropTable( - name: "DreamDaemonSettings"); - - migrationBuilder.DropTable( - name: "DreamMakerSettings"); - - migrationBuilder.DropTable( - name: "InstanceUsers"); - - migrationBuilder.DropTable( - name: "Jobs"); - - migrationBuilder.DropTable( - name: "Logs"); - - migrationBuilder.DropTable( - name: "RepositorySettings"); - - migrationBuilder.DropTable( - name: "ServerSettings"); - - migrationBuilder.DropTable( - name: "TestMerges"); - - migrationBuilder.DropTable( - name: "ChatSettings"); - - migrationBuilder.DropTable( - name: "CompileJobs"); - - migrationBuilder.DropTable( - name: "Instances"); - - migrationBuilder.DropTable( - name: "RevisionInformations"); - - migrationBuilder.DropTable( - name: "Users"); - } - } -} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServer/SqlServerDatabaseContextModelSnapshot.cs deleted file mode 100644 index 46332b16f3..0000000000 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServer/SqlServerDatabaseContextModelSnapshot.cs +++ /dev/null @@ -1,511 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Tgstation.Server.Host.Models; - -namespace Tgstation.Server.Host.Models.Migrations.SqlServer -{ - [DbContext(typeof(SqlServerDatabaseContext))] - partial class SqlServerDatabaseContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-preview2-30571") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ChatSettingsId"); - - b.Property("ChatSettingsId1"); - - b.Property("DiscordChannelId"); - - b.Property("IrcChannel"); - - b.HasKey("Id"); - - b.HasIndex("ChatSettingsId"); - - b.HasIndex("ChatSettingsId1"); - - b.ToTable("ChatChannels"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("DiscordBotToken"); - - b.Property("DiscordEnabled"); - - b.Property("InstanceId"); - - b.Property("IrcEnabled"); - - b.Property("IrcHost") - .IsRequired(); - - b.Property("IrcNickServPassword"); - - b.Property("IrcPort"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("ChatSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ExitCode"); - - b.Property("FinishedAt"); - - b.Property("InstanceId"); - - b.Property("Output"); - - b.Property("RevisionInformationId"); - - b.Property("StartedAt"); - - b.Property("TargetedPrimaryDirectory"); - - b.Property("TriggeredById"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("RevisionInformationId"); - - b.HasIndex("TriggeredById"); - - b.ToTable("CompileJobs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessToken"); - - b.Property("AllowWebClient"); - - b.Property("AutoStart"); - - b.Property("CompileJobId"); - - b.Property("InstanceId"); - - b.Property("PrimaryPort"); - - b.Property("ProcessId"); - - b.Property("SecondaryPort"); - - b.Property("SecurityLevel"); - - b.Property("SoftRestart"); - - b.Property("SoftShutdown"); - - b.HasKey("Id"); - - b.HasIndex("CompileJobId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("DreamDaemonSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AutoCompileInterval"); - - b.Property("CompileJobId"); - - b.Property("InstanceId"); - - b.Property("TargetDme"); - - b.HasKey("Id"); - - b.HasIndex("CompileJobId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("DreamMakerSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConfigurationAllowed"); - - b.Property("Name") - .IsRequired(); - - b.Property("Online"); - - b.Property("Path") - .IsRequired(); - - b.HasKey("Id"); - - b.ToTable("Instances"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ByondRights"); - - b.Property("ChatSettingsRights"); - - b.Property("ConfigurationRights"); - - b.Property("DreamDaemonRights"); - - b.Property("DreamMakerRights"); - - b.Property("InstanceId"); - - b.Property("RepositoryRights"); - - b.Property("UserId"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("UserId"); - - b.ToTable("InstanceUsers"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Cancelled"); - - b.Property("Description") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("StartedAt"); - - b.Property("StartedById"); - - b.Property("StoppedAt"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("StartedById"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessToken"); - - b.Property("AccessUser"); - - b.Property("AutoUpdateInterval"); - - b.Property("CommitterEmail") - .IsRequired(); - - b.Property("CommitterName") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("Origin"); - - b.Property("PushTestMergeCommits"); - - b.Property("RevisionInformationId"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.HasIndex("RevisionInformationId"); - - b.ToTable("RepositorySettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("OriginRevision") - .IsRequired() - .HasMaxLength(40); - - b.Property("Revision") - .IsRequired() - .HasMaxLength(40); - - b.HasKey("Id"); - - b.HasIndex("Revision") - .IsUnique(); - - b.ToTable("RevisionInformations"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ServerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("EnableTelemetry"); - - b.Property("SystemAuthenticationGroup"); - - b.Property("UpstreamRepository"); - - b.HasKey("Id"); - - b.ToTable("ServerSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Author") - .IsRequired(); - - b.Property("BodyAtMerge") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("MergedAt"); - - b.Property("MergedById"); - - b.Property("RevisionInformationId"); - - b.Property("TitleAtMerge") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("MergedById"); - - b.HasIndex("RevisionInformationId"); - - b.ToTable("TestMerges"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AdministrationRights"); - - b.Property("CreatedAt"); - - b.Property("InstanceManagerRights"); - - b.Property("Name") - .IsRequired(); - - b.Property("PasswordHash"); - - b.Property("SystemIdentifier"); - - b.Property("TokenSecret") - .HasMaxLength(40); - - b.HasKey("Id"); - - b.HasIndex("SystemIdentifier") - .IsUnique() - .HasFilter("[SystemIdentifier] IS NOT NULL"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("ZNetCS.AspNetCore.Logging.EntityFrameworkCore.Log", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("EventId"); - - b.Property("Level"); - - b.Property("Message"); - - b.Property("Name") - .HasMaxLength(255); - - b.Property("TimeStamp"); - - b.HasKey("Id"); - - b.ToTable("Logs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.HasOne("Tgstation.Server.Host.Models.ChatSettings") - .WithMany("AdminChannels") - .HasForeignKey("ChatSettingsId"); - - b.HasOne("Tgstation.Server.Host.Models.ChatSettings") - .WithMany("GeneralChannels") - .HasForeignKey("ChatSettingsId1"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("ChatSettings") - .HasForeignKey("Tgstation.Server.Host.Models.ChatSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("CompileJobs") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany() - .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "TriggeredBy") - .WithMany() - .HasForeignKey("TriggeredById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamDaemonSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamMakerSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("InstanceUsers") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.User") - .WithMany("InstanceUsers") - .HasForeignKey("UserId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("Jobs") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") - .WithMany() - .HasForeignKey("StartedById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("RepositorySettings") - .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany() - .HasForeignKey("RevisionInformationId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("TestMerges") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") - .WithMany() - .HasForeignKey("MergedById") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation") - .WithMany("TestMerges") - .HasForeignKey("RevisionInformationId"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.Designer.cs deleted file mode 100644 index 0194884160..0000000000 --- a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.Designer.cs +++ /dev/null @@ -1,510 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Tgstation.Server.Host.Models; - -namespace Tgstation.Server.Host.Models.Migrations.Sqlite -{ - [DbContext(typeof(SqliteDatabaseContext))] - [Migration("20180410204120_InitialCreate")] - partial class InitialCreate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-preview2-30571"); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ChatSettingsId"); - - b.Property("ChatSettingsId1"); - - b.Property("DiscordChannelId"); - - b.Property("IrcChannel"); - - b.HasKey("Id"); - - b.HasIndex("ChatSettingsId"); - - b.HasIndex("ChatSettingsId1"); - - b.ToTable("ChatChannels"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("DiscordBotToken"); - - b.Property("DiscordEnabled"); - - b.Property("InstanceId"); - - b.Property("IrcEnabled"); - - b.Property("IrcHost") - .IsRequired(); - - b.Property("IrcNickServPassword"); - - b.Property("IrcPort"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("ChatSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ExitCode"); - - b.Property("FinishedAt"); - - b.Property("InstanceId"); - - b.Property("Output"); - - b.Property("RevisionInformationId"); - - b.Property("StartedAt"); - - b.Property("TargetedPrimaryDirectory"); - - b.Property("TriggeredById"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("RevisionInformationId"); - - b.HasIndex("TriggeredById"); - - b.ToTable("CompileJobs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessToken"); - - b.Property("AllowWebClient"); - - b.Property("AutoStart"); - - b.Property("CompileJobId"); - - b.Property("InstanceId"); - - b.Property("PrimaryPort"); - - b.Property("ProcessId"); - - b.Property("SecondaryPort"); - - b.Property("SecurityLevel"); - - b.Property("SoftRestart"); - - b.Property("SoftShutdown"); - - b.HasKey("Id"); - - b.HasIndex("CompileJobId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("DreamDaemonSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AutoCompileInterval"); - - b.Property("CompileJobId"); - - b.Property("InstanceId"); - - b.Property("TargetDme"); - - b.HasKey("Id"); - - b.HasIndex("CompileJobId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("DreamMakerSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConfigurationAllowed"); - - b.Property("Name") - .IsRequired(); - - b.Property("Online"); - - b.Property("Path") - .IsRequired(); - - b.HasKey("Id"); - - b.ToTable("Instances"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ByondRights"); - - b.Property("ChatSettingsRights"); - - b.Property("ConfigurationRights"); - - b.Property("DreamDaemonRights"); - - b.Property("DreamMakerRights"); - - b.Property("InstanceId"); - - b.Property("RepositoryRights"); - - b.Property("UserId"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("UserId"); - - b.ToTable("InstanceUsers"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Cancelled"); - - b.Property("Description") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("StartedAt"); - - b.Property("StartedById"); - - b.Property("StoppedAt"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("StartedById"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessToken"); - - b.Property("AccessUser"); - - b.Property("AutoUpdateInterval"); - - b.Property("CommitterEmail") - .IsRequired(); - - b.Property("CommitterName") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("Origin"); - - b.Property("PushTestMergeCommits"); - - b.Property("RevisionInformationId"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.HasIndex("RevisionInformationId"); - - b.ToTable("RepositorySettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("OriginRevision") - .IsRequired() - .HasMaxLength(40); - - b.Property("Revision") - .IsRequired() - .HasMaxLength(40); - - b.HasKey("Id"); - - b.HasIndex("Revision") - .IsUnique(); - - b.ToTable("RevisionInformations"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ServerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("EnableTelemetry"); - - b.Property("SystemAuthenticationGroup"); - - b.Property("UpstreamRepository"); - - b.HasKey("Id"); - - b.ToTable("ServerSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Author") - .IsRequired(); - - b.Property("BodyAtMerge") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("MergedAt"); - - b.Property("MergedById"); - - b.Property("RevisionInformationId"); - - b.Property("TitleAtMerge") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("MergedById"); - - b.HasIndex("RevisionInformationId"); - - b.ToTable("TestMerges"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AdministrationRights"); - - b.Property("CreatedAt"); - - b.Property("InstanceManagerRights"); - - b.Property("Name") - .IsRequired(); - - b.Property("PasswordHash"); - - b.Property("SystemIdentifier"); - - b.Property("TokenSecret") - .HasMaxLength(40); - - b.HasKey("Id"); - - b.HasIndex("SystemIdentifier") - .IsUnique(); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("ZNetCS.AspNetCore.Logging.EntityFrameworkCore.Log", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("EventId"); - - b.Property("Level"); - - b.Property("Message"); - - b.Property("Name") - .HasMaxLength(255); - - b.Property("TimeStamp"); - - b.HasKey("Id"); - - b.ToTable("Logs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.HasOne("Tgstation.Server.Host.Models.ChatSettings") - .WithMany("AdminChannels") - .HasForeignKey("ChatSettingsId"); - - b.HasOne("Tgstation.Server.Host.Models.ChatSettings") - .WithMany("GeneralChannels") - .HasForeignKey("ChatSettingsId1"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("ChatSettings") - .HasForeignKey("Tgstation.Server.Host.Models.ChatSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("CompileJobs") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany() - .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "TriggeredBy") - .WithMany() - .HasForeignKey("TriggeredById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamDaemonSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamMakerSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("InstanceUsers") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.User") - .WithMany("InstanceUsers") - .HasForeignKey("UserId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("Jobs") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") - .WithMany() - .HasForeignKey("StartedById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("RepositorySettings") - .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany() - .HasForeignKey("RevisionInformationId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("TestMerges") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") - .WithMany() - .HasForeignKey("MergedById") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation") - .WithMany("TestMerges") - .HasForeignKey("RevisionInformationId"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.cs deleted file mode 100644 index 433fd2978e..0000000000 --- a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/20180410204120_InitialCreate.cs +++ /dev/null @@ -1,535 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Tgstation.Server.Host.Models.Migrations.Sqlite -{ - public partial class InitialCreate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Instances", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(nullable: false), - Path = table.Column(nullable: false), - Online = table.Column(nullable: false), - ConfigurationAllowed = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Instances", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Logs", - columns: table => new - { - EventId = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Level = table.Column(nullable: false), - Message = table.Column(nullable: true), - Name = table.Column(maxLength: 255, nullable: true), - TimeStamp = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Logs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "RevisionInformations", - columns: table => new - { - Revision = table.Column(maxLength: 40, nullable: false), - OriginRevision = table.Column(maxLength: 40, nullable: false), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true) - }, - constraints: table => - { - table.PrimaryKey("PK_RevisionInformations", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ServerSettings", - columns: table => new - { - SystemAuthenticationGroup = table.Column(nullable: true), - EnableTelemetry = table.Column(nullable: false), - UpstreamRepository = table.Column(nullable: true), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true) - }, - constraints: table => - { - table.PrimaryKey("PK_ServerSettings", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - AdministrationRights = table.Column(nullable: false), - CreatedAt = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - InstanceManagerRights = table.Column(nullable: false), - SystemIdentifier = table.Column(nullable: true), - Name = table.Column(nullable: false), - PasswordHash = table.Column(nullable: true), - TokenSecret = table.Column(maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ChatSettings", - columns: table => new - { - IrcEnabled = table.Column(nullable: false), - IrcHost = table.Column(nullable: false), - IrcPort = table.Column(nullable: false), - IrcNickServPassword = table.Column(nullable: true), - DiscordEnabled = table.Column(nullable: false), - DiscordBotToken = table.Column(nullable: true), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - InstanceId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ChatSettings", x => x.Id); - table.ForeignKey( - name: "FK_ChatSettings_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "RepositorySettings", - columns: table => new - { - Origin = table.Column(nullable: true), - CommitterName = table.Column(nullable: false), - CommitterEmail = table.Column(nullable: false), - AccessUser = table.Column(nullable: true), - AccessToken = table.Column(nullable: true), - PushTestMergeCommits = table.Column(nullable: false), - AutoUpdateInterval = table.Column(nullable: true), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - InstanceId = table.Column(nullable: false), - RevisionInformationId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_RepositorySettings", x => x.Id); - table.ForeignKey( - name: "FK_RepositorySettings_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_RepositorySettings_RevisionInformations_RevisionInformationId", - column: x => x.RevisionInformationId, - principalTable: "RevisionInformations", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "CompileJobs", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - StartedAt = table.Column(nullable: false), - FinishedAt = table.Column(nullable: false), - TargetedPrimaryDirectory = table.Column(nullable: true), - Output = table.Column(nullable: true), - ExitCode = table.Column(nullable: true), - TriggeredById = table.Column(nullable: false), - RevisionInformationId = table.Column(nullable: false), - InstanceId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_CompileJobs", x => x.Id); - table.ForeignKey( - name: "FK_CompileJobs_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_CompileJobs_RevisionInformations_RevisionInformationId", - column: x => x.RevisionInformationId, - principalTable: "RevisionInformations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_CompileJobs_Users_TriggeredById", - column: x => x.TriggeredById, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "InstanceUsers", - columns: table => new - { - ByondRights = table.Column(nullable: false), - DreamDaemonRights = table.Column(nullable: false), - DreamMakerRights = table.Column(nullable: false), - RepositoryRights = table.Column(nullable: false), - ChatSettingsRights = table.Column(nullable: false), - ConfigurationRights = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - InstanceId = table.Column(nullable: true), - UserId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_InstanceUsers", x => x.Id); - table.ForeignKey( - name: "FK_InstanceUsers_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_InstanceUsers_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Jobs", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Description = table.Column(nullable: false), - StartedAt = table.Column(nullable: false), - StoppedAt = table.Column(nullable: false), - Cancelled = table.Column(nullable: false), - StartedById = table.Column(nullable: false), - InstanceId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Jobs", x => x.Id); - table.ForeignKey( - name: "FK_Jobs_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Jobs_Users_StartedById", - column: x => x.StartedById, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "TestMerges", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - MergedAt = table.Column(nullable: false), - TitleAtMerge = table.Column(nullable: false), - BodyAtMerge = table.Column(nullable: false), - Author = table.Column(nullable: false), - MergedById = table.Column(nullable: false), - InstanceId = table.Column(nullable: true), - RevisionInformationId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TestMerges", x => x.Id); - table.ForeignKey( - name: "FK_TestMerges_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_TestMerges_Users_MergedById", - column: x => x.MergedById, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_TestMerges_RevisionInformations_RevisionInformationId", - column: x => x.RevisionInformationId, - principalTable: "RevisionInformations", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "ChatChannels", - columns: table => new - { - IrcChannel = table.Column(nullable: true), - DiscordChannelId = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ChatSettingsId = table.Column(nullable: true), - ChatSettingsId1 = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_ChatChannels", x => x.Id); - table.ForeignKey( - name: "FK_ChatChannels_ChatSettings_ChatSettingsId", - column: x => x.ChatSettingsId, - principalTable: "ChatSettings", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_ChatChannels_ChatSettings_ChatSettingsId1", - column: x => x.ChatSettingsId1, - principalTable: "ChatSettings", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "DreamDaemonSettings", - columns: table => new - { - AutoStart = table.Column(nullable: false), - AllowWebClient = table.Column(nullable: false), - SoftRestart = table.Column(nullable: false), - SoftShutdown = table.Column(nullable: false), - SecurityLevel = table.Column(nullable: false), - PrimaryPort = table.Column(nullable: false), - SecondaryPort = table.Column(nullable: false), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ProcessId = table.Column(nullable: true), - AccessToken = table.Column(nullable: true), - InstanceId = table.Column(nullable: false), - CompileJobId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id); - table.ForeignKey( - name: "FK_DreamDaemonSettings_CompileJobs_CompileJobId", - column: x => x.CompileJobId, - principalTable: "CompileJobs", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_DreamDaemonSettings_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "DreamMakerSettings", - columns: table => new - { - AutoCompileInterval = table.Column(nullable: true), - TargetDme = table.Column(nullable: true), - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - InstanceId = table.Column(nullable: false), - CompileJobId = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_DreamMakerSettings", x => x.Id); - table.ForeignKey( - name: "FK_DreamMakerSettings_CompileJobs_CompileJobId", - column: x => x.CompileJobId, - principalTable: "CompileJobs", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_DreamMakerSettings_Instances_InstanceId", - column: x => x.InstanceId, - principalTable: "Instances", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_ChatChannels_ChatSettingsId", - table: "ChatChannels", - column: "ChatSettingsId"); - - migrationBuilder.CreateIndex( - name: "IX_ChatChannels_ChatSettingsId1", - table: "ChatChannels", - column: "ChatSettingsId1"); - - migrationBuilder.CreateIndex( - name: "IX_ChatSettings_InstanceId", - table: "ChatSettings", - column: "InstanceId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_CompileJobs_InstanceId", - table: "CompileJobs", - column: "InstanceId"); - - migrationBuilder.CreateIndex( - name: "IX_CompileJobs_RevisionInformationId", - table: "CompileJobs", - column: "RevisionInformationId"); - - migrationBuilder.CreateIndex( - name: "IX_CompileJobs_TriggeredById", - table: "CompileJobs", - column: "TriggeredById"); - - migrationBuilder.CreateIndex( - name: "IX_DreamDaemonSettings_CompileJobId", - table: "DreamDaemonSettings", - column: "CompileJobId"); - - migrationBuilder.CreateIndex( - name: "IX_DreamDaemonSettings_InstanceId", - table: "DreamDaemonSettings", - column: "InstanceId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_DreamMakerSettings_CompileJobId", - table: "DreamMakerSettings", - column: "CompileJobId"); - - migrationBuilder.CreateIndex( - name: "IX_DreamMakerSettings_InstanceId", - table: "DreamMakerSettings", - column: "InstanceId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_InstanceUsers_InstanceId", - table: "InstanceUsers", - column: "InstanceId"); - - migrationBuilder.CreateIndex( - name: "IX_InstanceUsers_UserId", - table: "InstanceUsers", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_Jobs_InstanceId", - table: "Jobs", - column: "InstanceId"); - - migrationBuilder.CreateIndex( - name: "IX_Jobs_StartedById", - table: "Jobs", - column: "StartedById"); - - migrationBuilder.CreateIndex( - name: "IX_RepositorySettings_InstanceId", - table: "RepositorySettings", - column: "InstanceId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_RepositorySettings_RevisionInformationId", - table: "RepositorySettings", - column: "RevisionInformationId"); - - migrationBuilder.CreateIndex( - name: "IX_RevisionInformations_Revision", - table: "RevisionInformations", - column: "Revision", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_TestMerges_InstanceId", - table: "TestMerges", - column: "InstanceId"); - - migrationBuilder.CreateIndex( - name: "IX_TestMerges_MergedById", - table: "TestMerges", - column: "MergedById"); - - migrationBuilder.CreateIndex( - name: "IX_TestMerges_RevisionInformationId", - table: "TestMerges", - column: "RevisionInformationId"); - - migrationBuilder.CreateIndex( - name: "IX_Users_SystemIdentifier", - table: "Users", - column: "SystemIdentifier", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ChatChannels"); - - migrationBuilder.DropTable( - name: "DreamDaemonSettings"); - - migrationBuilder.DropTable( - name: "DreamMakerSettings"); - - migrationBuilder.DropTable( - name: "InstanceUsers"); - - migrationBuilder.DropTable( - name: "Jobs"); - - migrationBuilder.DropTable( - name: "Logs"); - - migrationBuilder.DropTable( - name: "RepositorySettings"); - - migrationBuilder.DropTable( - name: "ServerSettings"); - - migrationBuilder.DropTable( - name: "TestMerges"); - - migrationBuilder.DropTable( - name: "ChatSettings"); - - migrationBuilder.DropTable( - name: "CompileJobs"); - - migrationBuilder.DropTable( - name: "Instances"); - - migrationBuilder.DropTable( - name: "RevisionInformations"); - - migrationBuilder.DropTable( - name: "Users"); - } - } -} diff --git a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs deleted file mode 100644 index 499518c925..0000000000 --- a/src/Tgstation.Server.Host/Models/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs +++ /dev/null @@ -1,509 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Tgstation.Server.Host.Models; - -namespace Tgstation.Server.Host.Models.Migrations.Sqlite -{ - [DbContext(typeof(SqliteDatabaseContext))] - partial class SqliteDatabaseContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.0-preview2-30571"); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ChatSettingsId"); - - b.Property("ChatSettingsId1"); - - b.Property("DiscordChannelId"); - - b.Property("IrcChannel"); - - b.HasKey("Id"); - - b.HasIndex("ChatSettingsId"); - - b.HasIndex("ChatSettingsId1"); - - b.ToTable("ChatChannels"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("DiscordBotToken"); - - b.Property("DiscordEnabled"); - - b.Property("InstanceId"); - - b.Property("IrcEnabled"); - - b.Property("IrcHost") - .IsRequired(); - - b.Property("IrcNickServPassword"); - - b.Property("IrcPort"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("ChatSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ExitCode"); - - b.Property("FinishedAt"); - - b.Property("InstanceId"); - - b.Property("Output"); - - b.Property("RevisionInformationId"); - - b.Property("StartedAt"); - - b.Property("TargetedPrimaryDirectory"); - - b.Property("TriggeredById"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("RevisionInformationId"); - - b.HasIndex("TriggeredById"); - - b.ToTable("CompileJobs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessToken"); - - b.Property("AllowWebClient"); - - b.Property("AutoStart"); - - b.Property("CompileJobId"); - - b.Property("InstanceId"); - - b.Property("PrimaryPort"); - - b.Property("ProcessId"); - - b.Property("SecondaryPort"); - - b.Property("SecurityLevel"); - - b.Property("SoftRestart"); - - b.Property("SoftShutdown"); - - b.HasKey("Id"); - - b.HasIndex("CompileJobId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("DreamDaemonSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AutoCompileInterval"); - - b.Property("CompileJobId"); - - b.Property("InstanceId"); - - b.Property("TargetDme"); - - b.HasKey("Id"); - - b.HasIndex("CompileJobId"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.ToTable("DreamMakerSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConfigurationAllowed"); - - b.Property("Name") - .IsRequired(); - - b.Property("Online"); - - b.Property("Path") - .IsRequired(); - - b.HasKey("Id"); - - b.ToTable("Instances"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ByondRights"); - - b.Property("ChatSettingsRights"); - - b.Property("ConfigurationRights"); - - b.Property("DreamDaemonRights"); - - b.Property("DreamMakerRights"); - - b.Property("InstanceId"); - - b.Property("RepositoryRights"); - - b.Property("UserId"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("UserId"); - - b.ToTable("InstanceUsers"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Cancelled"); - - b.Property("Description") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("StartedAt"); - - b.Property("StartedById"); - - b.Property("StoppedAt"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("StartedById"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessToken"); - - b.Property("AccessUser"); - - b.Property("AutoUpdateInterval"); - - b.Property("CommitterEmail") - .IsRequired(); - - b.Property("CommitterName") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("Origin"); - - b.Property("PushTestMergeCommits"); - - b.Property("RevisionInformationId"); - - b.HasKey("Id"); - - b.HasIndex("InstanceId") - .IsUnique(); - - b.HasIndex("RevisionInformationId"); - - b.ToTable("RepositorySettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("OriginRevision") - .IsRequired() - .HasMaxLength(40); - - b.Property("Revision") - .IsRequired() - .HasMaxLength(40); - - b.HasKey("Id"); - - b.HasIndex("Revision") - .IsUnique(); - - b.ToTable("RevisionInformations"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ServerSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("EnableTelemetry"); - - b.Property("SystemAuthenticationGroup"); - - b.Property("UpstreamRepository"); - - b.HasKey("Id"); - - b.ToTable("ServerSettings"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Author") - .IsRequired(); - - b.Property("BodyAtMerge") - .IsRequired(); - - b.Property("InstanceId"); - - b.Property("MergedAt"); - - b.Property("MergedById"); - - b.Property("RevisionInformationId"); - - b.Property("TitleAtMerge") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("InstanceId"); - - b.HasIndex("MergedById"); - - b.HasIndex("RevisionInformationId"); - - b.ToTable("TestMerges"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AdministrationRights"); - - b.Property("CreatedAt"); - - b.Property("InstanceManagerRights"); - - b.Property("Name") - .IsRequired(); - - b.Property("PasswordHash"); - - b.Property("SystemIdentifier"); - - b.Property("TokenSecret") - .HasMaxLength(40); - - b.HasKey("Id"); - - b.HasIndex("SystemIdentifier") - .IsUnique(); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("ZNetCS.AspNetCore.Logging.EntityFrameworkCore.Log", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("EventId"); - - b.Property("Level"); - - b.Property("Message"); - - b.Property("Name") - .HasMaxLength(255); - - b.Property("TimeStamp"); - - b.HasKey("Id"); - - b.ToTable("Logs"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => - { - b.HasOne("Tgstation.Server.Host.Models.ChatSettings") - .WithMany("AdminChannels") - .HasForeignKey("ChatSettingsId"); - - b.HasOne("Tgstation.Server.Host.Models.ChatSettings") - .WithMany("GeneralChannels") - .HasForeignKey("ChatSettingsId1"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.ChatSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("ChatSettings") - .HasForeignKey("Tgstation.Server.Host.Models.ChatSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("CompileJobs") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany() - .HasForeignKey("RevisionInformationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "TriggeredBy") - .WithMany() - .HasForeignKey("TriggeredById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamDaemonSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") - .WithMany() - .HasForeignKey("CompileJobId"); - - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("DreamMakerSettings") - .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("InstanceUsers") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.User") - .WithMany("InstanceUsers") - .HasForeignKey("UserId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithMany("Jobs") - .HasForeignKey("InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") - .WithMany() - .HasForeignKey("StartedById") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") - .WithOne("RepositorySettings") - .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") - .WithMany() - .HasForeignKey("RevisionInformationId"); - }); - - modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => - { - b.HasOne("Tgstation.Server.Host.Models.Instance") - .WithMany("TestMerges") - .HasForeignKey("InstanceId"); - - b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") - .WithMany() - .HasForeignKey("MergedById") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Tgstation.Server.Host.Models.RevisionInformation") - .WithMany("TestMerges") - .HasForeignKey("RevisionInformationId"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs index c46d26c1ba..e88678bf23 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -6,17 +6,17 @@ using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Models { /// - /// for MySQL + /// for MySQL /// sealed class MySqlDatabaseContext : DatabaseContext { /// /// Construct a /// - /// The for the - /// The of for the - /// The for the - /// The for the + /// The for the + /// The of for the + /// The for the + /// The for the public MySqlDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) { } diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs index 7003c01085..240f8ba1e7 100644 --- a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs @@ -6,17 +6,17 @@ using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Models { /// - /// for Sqlserver + /// for Sqlserver /// sealed class SqlServerDatabaseContext : DatabaseContext { /// /// Construct a /// - /// The for the - /// The of for the - /// The for the - /// The for the + /// The for the + /// The of for the + /// The for the + /// The for the public SqlServerDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) { } diff --git a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs index 3581d3d130..0d6c1c94e3 100644 --- a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs @@ -6,17 +6,17 @@ using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Models { /// - /// for Sqlite + /// for Sqlite /// sealed class SqliteDatabaseContext : DatabaseContext { /// /// Construct a /// - /// The for the - /// The of for the - /// The for the - /// The for the + /// The for the + /// The of for the + /// The for the + /// The for the public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) { } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index be2d1ae09d..139f940917 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -9,10 +9,13 @@ namespace Tgstation.Server.Host.Models /// sealed class User : Api.Models.Internal.User { + /// + /// The hash of the user's password + /// public string PasswordHash { get; set; } /// - /// The value used for the to encrypt JWTs + /// The value used to encrypt JWTs /// [StringLength(CryptographySuite.SecureStringLength)] public string TokenSecret { get; set; } From 9f942f295e06e7a657e892f22592d5fbd8907554 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 11 Apr 2018 13:35:32 -0400 Subject: [PATCH 079/397] Write all XML doc comments --- src/Tgstation.Server.Api/Models/Instance.cs | 2 +- .../Models/Internal/Job.cs | 2 +- .../Models/Internal/User.cs | 6 ++++ src/Tgstation.Server.Api/Models/User.cs | 4 +-- .../Configuration/DatabaseConfiguration.cs | 14 +++++++++ .../Configuration/DatabaseType.cs | 12 +++++++ src/Tgstation.Server.Host/Core/Application.cs | 9 +++--- .../Core/ApplicationBuilderExtensions.cs | 13 +++++--- .../Core/AuthenticationContext.cs | 7 +++-- .../Core/HttpContextExtensions.cs | 13 +++++--- .../Core/IAuthenticationContext.cs | 6 ++-- .../Core/IServerUpdateConsumer.cs | 7 +++++ .../Core/ISystemIdentity.cs | 18 +++++++++++ .../Core/ISystemIdentityFactory.cs | 17 +++++++++- .../Core/ITokenManager.cs | 24 ++++++++++---- .../Models/ChatChannel.cs | 3 +- .../Models/ChatSettings.cs | 18 ++++++++++- .../Models/CompileJob.cs | 7 +++++ .../Models/DatabaseContext.cs | 31 +++++++++++++++++++ .../Models/DatabaseSeeder.cs | 3 +- .../Models/DreamDaemonSettings.cs | 21 ++++++++++++- .../Models/DreamMakerSettings.cs | 17 ++++++++-- src/Tgstation.Server.Host/Models/Instance.cs | 7 ++++- .../Models/InstanceUser.cs | 4 +++ src/Tgstation.Server.Host/Models/Job.cs | 7 +++-- .../Models/RepositorySettings.cs | 15 ++++++++- .../Models/RevisionInformation.cs | 9 +++++- .../Models/ServerSettings.cs | 8 +++-- src/Tgstation.Server.Host/Models/TestMerge.cs | 7 +++-- src/Tgstation.Server.Host/Models/User.cs | 10 ++++-- src/Tgstation.Server.Host/ServerFactory.cs | 1 - 31 files changed, 270 insertions(+), 52 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index c1f198d30a..bde9f13522 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -23,7 +23,7 @@ namespace Tgstation.Server.Api.Models public string Name { get; set; } /// - /// The path to where the is located + /// The path to where the is located. Changing this will temporarily offline the while it moves /// [Permissions(WriteRight = InstanceManagerRights.Relocate)] [Required] diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index a3f38d806d..b8ebe63b70 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Represents a long running job /// - [Model] + [Model(RequiresInstance = true)] public class Job { /// diff --git a/src/Tgstation.Server.Api/Models/Internal/User.cs b/src/Tgstation.Server.Api/Models/Internal/User.cs index 2a62da30b2..e8c137c04a 100644 --- a/src/Tgstation.Server.Api/Models/Internal/User.cs +++ b/src/Tgstation.Server.Api/Models/Internal/User.cs @@ -16,6 +16,12 @@ namespace Tgstation.Server.Api.Models.Internal [Permissions(DenyWrite = true)] public long Id { get; set; } + /// + /// If the is enabled since users cannot be deleted. System users cannot be disabled + /// + [Permissions(WriteRight = AdministrationRights.EditUsers)] + public bool Enabled { get; set; } + /// /// When the was created /// diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index 78bdb3e934..83fac4e82f 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -4,9 +4,9 @@ public sealed class User : Internal.User { /// - /// If the is enabled since users cannot be deleted + /// The who created this /// [Permissions(DenyWrite = true)] - public bool Enabled { get; set; } + public User CreatedBy { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index ef28e52f8e..4afebe14a5 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -1,9 +1,23 @@ namespace Tgstation.Server.Host.Configuration { + /// + /// Configuration options for the + /// sealed class DatabaseConfiguration { + /// + /// The key for the the resides in + /// public const string Section = "Database"; + + /// + /// The to create + /// public DatabaseType DatabaseType { get; set; } + + /// + /// The connection string for the database + /// public string ConnectionString { get; set; } } } diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs index 919c82ed47..bbc8be7798 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs @@ -1,9 +1,21 @@ namespace Tgstation.Server.Host.Configuration { + /// + /// Type of database to user + /// enum DatabaseType { + /// + /// Use Microsoft SQL Server + /// SqlServer, + /// + /// Use MySQL/MariaDB + /// MySql, + /// + /// Use SQLite 3 + /// Sqlite } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 9f8f436f50..146d764b45 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -6,7 +6,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; -using System.Collections.Generic; using System.Globalization; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Models; @@ -78,7 +77,7 @@ namespace Tgstation.Server.Host.Core services.AddScoped(x => x.GetRequiredService()); break; default: - throw new InvalidOperationException("Invalid DatabaseType!"); + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}!", nameof(DatabaseType))); } services.AddSingleton(); @@ -100,11 +99,11 @@ namespace Tgstation.Server.Host.Core applicationBuilder.UseAsyncInitialization(async (cancellationToken) => { - using (var scop = applicationBuilder.ApplicationServices.CreateScope()) - await scop.ServiceProvider.GetRequiredService().Initialize(cancellationToken).ConfigureAwait(false); + using (var scope = applicationBuilder.ApplicationServices.CreateScope()) + await scope.ServiceProvider.GetRequiredService().Initialize(cancellationToken).ConfigureAwait(false); }); - applicationBuilder.UseSystemAuthentication(); + applicationBuilder.UseAuthentication(); applicationBuilder.UseMvc(); } diff --git a/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs index a1714d6b5d..343053cdfd 100644 --- a/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs @@ -1,13 +1,18 @@ using Microsoft.AspNetCore.Builder; -using System; -using System.Collections.Generic; -using System.Text; namespace Tgstation.Server.Host.Core { + /// + /// Extension methods for + /// static class ApplicationBuilderExtensions { - public static IApplicationBuilder UseSystemAuthentication(this IApplicationBuilder applicationBuilder) + /// + /// Apply the middleware + /// + /// The to add the middleware for + /// + public static IApplicationBuilder UseAuthentication(this IApplicationBuilder applicationBuilder) { AuthenticationContext.AddToPipeline(applicationBuilder); return applicationBuilder; diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs index 9ba9cd4867..4737b0ad9b 100644 --- a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Host.Models; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Core { @@ -55,7 +56,7 @@ namespace Tgstation.Server.Host.Core { if (headers.IsTokenAuthentication) { - var user = await tokenManager.GetUser(headers.Token, httpContext.RequestAborted).ConfigureAwait(false); + var user = await tokenManager.GetUser(new Token { Value = headers.Token }, httpContext.RequestAborted).ConfigureAwait(false); systemIdentity = systemIdentityFactory.CreateSystemIdentity(user); } else @@ -98,13 +99,13 @@ namespace Tgstation.Server.Host.Core public void Dispose() => SystemIdentity.Dispose(); /// - public Task User(CancellationToken cancellationToken) + public Task User(CancellationToken cancellationToken) { throw new NotImplementedException(); } /// - public Task InstanceUser(Instance instance, CancellationToken cancellationToken) + public Task InstanceUser(Models.Instance instance, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs b/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs index 7bca0daf5b..f81085d310 100644 --- a/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs +++ b/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs @@ -1,12 +1,17 @@ using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Text; namespace Tgstation.Server.Host.Core { + /// + /// Extension methods for + /// static class HttpContextExtensions { - public static IAuthenticationContext AuthenticationContext(this HttpContext httpContext) => Core.AuthenticationContext.Current(httpContext); + /// + /// Get the associated with the + /// + /// The containing the + /// The associated with the + public static IAuthenticationContext AuthenticationContext(this HttpContext httpContext) => Core.AuthenticationContext.Current(httpContext); } } diff --git a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs index 1d2466bbaf..81dc20535f 100644 --- a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs @@ -25,9 +25,9 @@ namespace Tgstation.Server.Host.Core /// /// The represented by and a given /// - /// The of the + /// The of the /// The for the operation - /// A resulting in the represented by and a given - Task InstanceUser(Instance instance, CancellationToken cancellationToken); + /// A resulting in the represented by and a given + Task InstanceUser(Instance instance, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs b/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs index b22df996ed..9055b7206e 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs +++ b/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs @@ -1,7 +1,14 @@ namespace Tgstation.Server.Host.Core { + /// + /// Represents a service that may take an updated assembly and run it, stopping the current assembly in the process + /// interface IServerUpdateConsumer { + /// + /// Run a new assembly and stop the current one. This will likely trigger all active s + /// + /// The path to the new assembly void ApplyUpdate(string updatePath); } } diff --git a/src/Tgstation.Server.Host/Core/ISystemIdentity.cs b/src/Tgstation.Server.Host/Core/ISystemIdentity.cs index 6f60a6bc99..8901917d98 100644 --- a/src/Tgstation.Server.Host/Core/ISystemIdentity.cs +++ b/src/Tgstation.Server.Host/Core/ISystemIdentity.cs @@ -3,12 +3,30 @@ using System.Collections.Generic; namespace Tgstation.Server.Host.Core { + /// + /// Represents a user on the current + /// interface ISystemIdentity : IDisposable { + /// + /// A unique identifier for the user + /// string Uid { get; } + + /// + /// The user's name + /// string Username { get; } + + /// + /// Groups the user is in + /// IEnumerable Groups { get; } + /// + /// Clone the creating another copy that must have called on it + /// + /// A new mirroring the current one ISystemIdentity Clone(); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs index 05e03ea829..a0de1cda52 100644 --- a/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs @@ -1,10 +1,25 @@ -using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { + /// + /// Factory for s + /// interface ISystemIdentityFactory { + /// + /// Create a for a given + /// + /// The user to create a for + /// A new ISystemIdentity CreateSystemIdentity(User user); + + /// + /// Create a for a given username and password + /// + /// The username of the user + /// The password of the user + /// A new ISystemIdentity CreateSystemIdentity(string username, string password); } } diff --git a/src/Tgstation.Server.Host/Core/ITokenManager.cs b/src/Tgstation.Server.Host/Core/ITokenManager.cs index e898760f51..d34def21a2 100644 --- a/src/Tgstation.Server.Host/Core/ITokenManager.cs +++ b/src/Tgstation.Server.Host/Core/ITokenManager.cs @@ -1,15 +1,27 @@ -using System.Collections.Generic; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Core { + /// + /// For creating and decoding JWTs + /// interface ITokenManager { - Task GetUser(string token, CancellationToken cancellationToken); - Task CreateToken(User user, CancellationToken cancellationToken); - Task> UserTokens(User user, CancellationToken cancellationToken); - Task> AllTokens(CancellationToken cancellationToken); + /// + /// Get the user associated with a + /// + /// The to get the user for + /// The for the operation + /// The associated with the + Task GetUser(Token token, CancellationToken cancellationToken); + + /// + /// Create a for a given + /// + /// The to create the token for. Must have the and fields available + /// A new + Token CreateToken(Models.User user); } } diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index 8429585cdf..4e6878da32 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -1,9 +1,10 @@ namespace Tgstation.Server.Host.Models { + /// sealed class ChatChannel : Api.Models.ChatChannel { /// - /// The column ID + /// The row Id /// public long Id { get; set; } } diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs index c62d8e23dd..8513dca245 100644 --- a/src/Tgstation.Server.Host/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -3,17 +3,33 @@ using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Host.Models { + /// sealed class ChatSettings : Api.Models.Internal.ChatSettings { + /// + /// The row Id + /// public long Id { get; set; } + /// + /// The + /// public long InstanceId { get; set; } + /// + /// The parent + /// [Required] public Instance Instance { get; set; } + /// + /// See + /// public List AdminChannels { get; set; } - + + /// + /// See + /// public List GeneralChannels { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 9e26c011e0..2b8404da46 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -2,11 +2,18 @@ namespace Tgstation.Server.Host.Models { + /// sealed class CompileJob : Api.Models.Internal.CompileJob { + /// + /// See + /// [Required] public User TriggeredBy { get; set; } + /// + /// See + /// [Required] public RevisionInformation RevisionInformation { get; set; } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index a429bf65eb..af4cd43664 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -26,15 +26,46 @@ namespace Tgstation.Server.Host.Models /// The for s /// public DbSet Logs { get; set; } + + /// + /// The s in the + /// public DbSet InstanceUsers { get; set; } + /// + /// The s in the + /// public DbSet ChatChannels { get; set; } + /// + /// The s in the + /// public DbSet ChatSettings { get; set; } + /// + /// The in the + /// public DbSet DreamDaemonSettings { get; set; } + /// + /// The in the + /// public DbSet DreamMakerSettings { get; set; } + /// + /// The s in the + /// public DbSet CompileJobs { get; set; } + /// + /// The s in the + /// public DbSet Jobs { get; set; } + /// + /// The s in the + /// public DbSet TestMerges { get; set; } + /// + /// The s in the + /// public DbSet RevisionInformations { get; set; } + /// + /// The in the + /// public DbSet RepositorySettings { get; set; } /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index 6a2cd85c0d..fd9f0b93de 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -38,7 +38,8 @@ namespace Tgstation.Server.Host.Models AdministrationRights = (AdministrationRights)~0, CreatedAt = DateTimeOffset.Now, InstanceManagerRights = (InstanceManagerRights)~0, - Name = "Admin" + Name = "Admin", + Enabled = true, }; cryptographySuite.RegenerateUserToken(admin); cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs index 2573093598..33d0246b77 100644 --- a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -2,19 +2,38 @@ namespace Tgstation.Server.Host.Models { + /// sealed class DreamDaemonSettings : Api.Models.Internal.DreamDaemonSettings { + /// + /// The row Id + /// public long Id { get; set; } + /// + /// The PID of a currently running DD instance + /// public int? ProcessId { get; set; } + /// + /// The access token used for communication with DD + /// public string AccessToken { get; set; } - + + /// + /// The + /// public long InstanceId { get; set; } + /// + /// The parent + /// [Required] public Instance Instance { get; set; } + /// + /// See + /// public CompileJob CompileJob { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index fb79315315..c6621a07f4 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -2,15 +2,28 @@ namespace Tgstation.Server.Host.Models { + /// sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings { + /// + /// The row Id + /// public long Id { get; set; } - + + /// + /// The + /// public long InstanceId { get; set; } + /// + /// The parent + /// [Required] public Instance Instance { get; set; } - public CompileJob CompileJob { get; set; } + /// + /// + /// + public CompileJob LastJob { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 0ecb141755..1732bcd3c0 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { @@ -21,9 +20,15 @@ namespace Tgstation.Server.Host.Models [Required] public DreamMakerSettings DreamMakerSettings { get; set; } + /// + /// The for the + /// [Required] public DreamDaemonSettings DreamDaemonSettings { get; set; } + /// + /// The for the + /// [Required] public RepositorySettings RepositorySettings { get; set; } diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 7794b2d357..e2c343cbb1 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -1,7 +1,11 @@ namespace Tgstation.Server.Host.Models { + /// sealed class InstanceUser : Api.Models.InstanceUser { + /// + /// The row Id + /// public long Id { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index e902cd9b96..005972d7d6 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -2,12 +2,13 @@ namespace Tgstation.Server.Host.Models { + /// sealed class Job : Api.Models.Internal.Job { + /// + /// See + /// [Required] public User StartedBy { get; set; } - - [Required] - public Instance Instance { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 782ef405db..5017df5343 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -2,15 +2,28 @@ namespace Tgstation.Server.Host.Models { + /// sealed class RepositorySettings : Api.Models.Internal.RepositorySettings { + /// + /// The row Id + /// public long Id { get; set; } + /// + /// The + /// public long InstanceId { get; set; } + /// + /// The parent + /// [Required] public Instance Instance { get; set; } - + + /// + /// See + /// public RevisionInformation RevisionInformation { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 5d5b9a4885..dddd11bb77 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -2,10 +2,17 @@ namespace Tgstation.Server.Host.Models { + /// sealed class RevisionInformation : Api.Models.Internal.RevisionInformation { + /// + /// The row Id + /// public long Id { get; set; } - + + /// + /// See + /// public List TestMerges { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/ServerSettings.cs b/src/Tgstation.Server.Host/Models/ServerSettings.cs index 8a84475dad..df05f75182 100644 --- a/src/Tgstation.Server.Host/Models/ServerSettings.cs +++ b/src/Tgstation.Server.Host/Models/ServerSettings.cs @@ -1,9 +1,11 @@ -using System.ComponentModel.DataAnnotations; - -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Models { + /// sealed class ServerSettings : Api.Models.Internal.ServerSettings { + /// + /// The row Id + /// public long Id { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index b334c2b844..ad2eab8783 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -2,11 +2,12 @@ namespace Tgstation.Server.Host.Models { - /// - /// Represent a merge of a live pull request - /// + /// sealed class TestMerge : Api.Models.Internal.TestMerge { + /// + /// See + /// [Required] public User MergedBy { get; set; } } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index 139f940917..9977f81067 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -4,9 +4,7 @@ using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Models { - /// - /// Represents a in the database - /// + /// sealed class User : Api.Models.Internal.User { /// @@ -20,6 +18,12 @@ namespace Tgstation.Server.Host.Models [StringLength(CryptographySuite.SecureStringLength)] public string TokenSecret { get; set; } + /// + /// See + /// + [Required] + public User CreatedBy { get; set; } + /// /// The s for the /// diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index f934789cf6..810ef924e2 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host From d9d736a05fe91e3c51869d31a7f80e809456145a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 11 Apr 2018 13:47:08 -0400 Subject: [PATCH 080/397] Fix bad define --- src/Tgstation.Server.Host/Models/DatabaseContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index af4cd43664..d48aeb4adb 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -142,8 +142,8 @@ namespace Tgstation.Server.Host.Models var migrations = await Database.GetAppliedMigrationsAsync().ConfigureAwait(false); var wasEmpty = !migrations.Any(); await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); -#endif if (wasEmpty) +#endif await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); } From 6c7b54018b1e24240084ce1d81fe7c11b7b7da1d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 11 Apr 2018 14:23:16 -0400 Subject: [PATCH 081/397] Further Db fixes --- src/Tgstation.Server.Api/Models/ChatChannel.cs | 5 +++++ src/Tgstation.Server.Api/Models/ChatSettings.cs | 10 ++-------- src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs | 8 ++------ src/Tgstation.Server.Host/Models/ChatChannel.cs | 10 ++++++++++ src/Tgstation.Server.Host/Models/ChatSettings.cs | 9 ++------- src/Tgstation.Server.Host/Models/DatabaseContext.cs | 7 ++++++- src/Tgstation.Server.Host/Models/DatabaseSeeder.cs | 1 + src/Tgstation.Server.Host/Models/Instance.cs | 4 ---- src/Tgstation.Server.Host/Models/User.cs | 6 +++++- 9 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index 1ad3c74629..a927090029 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -14,5 +14,10 @@ /// The Discord channel ID /// public long DiscordChannelId { get; set; } + + /// + /// If the is an admin channel + /// + public bool IsAdminChannel { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/ChatSettings.cs b/src/Tgstation.Server.Api/Models/ChatSettings.cs index a6350f0aa3..a4cb033170 100644 --- a/src/Tgstation.Server.Api/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/ChatSettings.cs @@ -6,16 +6,10 @@ namespace Tgstation.Server.Api.Models /// public sealed class ChatSettings : Internal.ChatSettings { - /// - /// Channels the bot should listen/announce in and allow admin commands - /// - [Permissions(WriteRight = ChatSettingsRights.SetDiscordChannels)] - public List AdminChannels { get; set; } - /// /// Channels the Discord bot should listen/announce in /// - [Permissions(WriteRight = ChatSettingsRights.SetDiscordChannels)] - public List GeneralChannels { get; set; } + [Permissions(WriteRight = ChatSettingsRights.SetChannels)] + public List Channels { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs index cade0e977e..d27723c28c 100644 --- a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs @@ -21,9 +21,9 @@ namespace Tgstation.Server.Api.Rights /// SetIrcSettings = 2, /// - /// User can change the IRC channels + /// User can change the chat channels /// - SetIrcChannels = 4, + SetChannels = 4, /// /// User can enable/disable the Discord bot /// @@ -32,9 +32,5 @@ namespace Tgstation.Server.Api.Rights /// User can change the Discord settings /// SetDiscordSettings = 16, - /// - /// User can change the Discord channels - /// - SetDiscordChannels = 32, } } diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index 4e6878da32..e091361f49 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -7,5 +7,15 @@ /// The row Id /// public long Id { get; set; } + + /// + /// The + /// + public long ChatSettingsId { get; set; } + + /// + /// The + /// + public ChatSettings ChatSettings { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs index 8513dca245..f81f562fa5 100644 --- a/src/Tgstation.Server.Host/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -23,13 +23,8 @@ namespace Tgstation.Server.Host.Models public Instance Instance { get; set; } /// - /// See + /// See /// - public List AdminChannels { get; set; } - - /// - /// See - /// - public List GeneralChannels { get; set; } + public List Channels { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index d48aeb4adb..d0738df579 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -110,7 +110,12 @@ namespace Tgstation.Server.Host.Models modelBuilder.Entity().ToTable(nameof(Logs)); modelBuilder.Entity().HasIndex(x => x.Revision).IsUnique(); - modelBuilder.Entity().HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique(); + var user = modelBuilder.Entity(); + user.HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique(); + user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict); + var chatChannel = modelBuilder.Entity(); + chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique(); + chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique(); } /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index fd9f0b93de..75d4615c3e 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Rights; diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 1732bcd3c0..59224b2edd 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -11,25 +11,21 @@ namespace Tgstation.Server.Host.Models /// /// The for the /// - [Required] public ChatSettings ChatSettings { get; set; } /// /// The for the /// - [Required] public DreamMakerSettings DreamMakerSettings { get; set; } /// /// The for the /// - [Required] public DreamDaemonSettings DreamDaemonSettings { get; set; } /// /// The for the /// - [Required] public RepositorySettings RepositorySettings { get; set; } /// diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index 9977f81067..5dc89d9e3b 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -21,9 +21,13 @@ namespace Tgstation.Server.Host.Models /// /// See /// - [Required] public User CreatedBy { get; set; } + /// + /// s created by this + /// + public List CreatedUsers { get; set; } + /// /// The s for the /// From 0237f78a5aeb24d9c4689bc766b027f163feacd0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 11 Apr 2018 14:26:57 -0400 Subject: [PATCH 082/397] Create Security namespace --- .../{Core => }/ApplicationBuilderExtensions.cs | 3 ++- src/Tgstation.Server.Host/Core/Application.cs | 1 + .../{Core => }/HttpContextExtensions.cs | 5 +++-- src/Tgstation.Server.Host/Models/DatabaseSeeder.cs | 3 +-- src/Tgstation.Server.Host/Models/User.cs | 2 +- .../{Core => Security}/AuthenticationContext.cs | 2 +- .../{Core => Security}/CryptographySuite.cs | 2 +- .../{Core => Security}/IAuthenticationContext.cs | 2 +- .../{Core => Security}/ICryptographySuite.cs | 2 +- .../{Core => Security}/ISystemIdentity.cs | 2 +- .../{Core => Security}/ISystemIdentityFactory.cs | 2 +- .../{Core => Security}/ITokenManager.cs | 2 +- 12 files changed, 15 insertions(+), 13 deletions(-) rename src/Tgstation.Server.Host/{Core => }/ApplicationBuilderExtensions.cs (90%) rename src/Tgstation.Server.Host/{Core => }/HttpContextExtensions.cs (79%) rename src/Tgstation.Server.Host/{Core => Security}/AuthenticationContext.cs (99%) rename src/Tgstation.Server.Host/{Core => Security}/CryptographySuite.cs (98%) rename src/Tgstation.Server.Host/{Core => Security}/IAuthenticationContext.cs (97%) rename src/Tgstation.Server.Host/{Core => Security}/ICryptographySuite.cs (95%) rename src/Tgstation.Server.Host/{Core => Security}/ISystemIdentity.cs (94%) rename src/Tgstation.Server.Host/{Core => Security}/ISystemIdentityFactory.cs (95%) rename src/Tgstation.Server.Host/{Core => Security}/ITokenManager.cs (96%) diff --git a/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/ApplicationBuilderExtensions.cs similarity index 90% rename from src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs rename to src/Tgstation.Server.Host/ApplicationBuilderExtensions.cs index 343053cdfd..36eaa08627 100644 --- a/src/Tgstation.Server.Host/Core/ApplicationBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/ApplicationBuilderExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Builder; +using Tgstation.Server.Host.Security; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host { /// /// Extension methods for diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 146d764b45..1f196815c9 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -9,6 +9,7 @@ using System; using System.Globalization; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Core { diff --git a/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs b/src/Tgstation.Server.Host/HttpContextExtensions.cs similarity index 79% rename from src/Tgstation.Server.Host/Core/HttpContextExtensions.cs rename to src/Tgstation.Server.Host/HttpContextExtensions.cs index f81085d310..e3677240e9 100644 --- a/src/Tgstation.Server.Host/Core/HttpContextExtensions.cs +++ b/src/Tgstation.Server.Host/HttpContextExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; +using Tgstation.Server.Host.Security; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host { /// /// Extension methods for @@ -12,6 +13,6 @@ namespace Tgstation.Server.Host.Core /// /// The containing the /// The associated with the - public static IAuthenticationContext AuthenticationContext(this HttpContext httpContext) => Core.AuthenticationContext.Current(httpContext); + public static IAuthenticationContext AuthenticationContext(this HttpContext httpContext) => Security.AuthenticationContext.Current(httpContext); } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index 75d4615c3e..bd6236c940 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Rights; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models { diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index 5dc89d9e3b..1264476db3 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models { diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs similarity index 99% rename from src/Tgstation.Server.Host/Core/AuthenticationContext.cs rename to src/Tgstation.Server.Host/Security/AuthenticationContext.cs index 4737b0ad9b..3e9ac45993 100644 --- a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -9,7 +9,7 @@ using Tgstation.Server.Api; using Tgstation.Server.Host.Models; using Tgstation.Server.Api.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Security { /// /// Manages s for a scope diff --git a/src/Tgstation.Server.Host/Core/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs similarity index 98% rename from src/Tgstation.Server.Host/Core/CryptographySuite.cs rename to src/Tgstation.Server.Host/Security/CryptographySuite.cs index 9cd7294780..7b04a11e4e 100644 --- a/src/Tgstation.Server.Host/Core/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -4,7 +4,7 @@ using System.Security.Cryptography; using System.Text; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Security { /// sealed class CryptographySuite : ICryptographySuite diff --git a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs similarity index 97% rename from src/Tgstation.Server.Host/Core/IAuthenticationContext.cs rename to src/Tgstation.Server.Host/Security/IAuthenticationContext.cs index 81dc20535f..1b5ce33483 100644 --- a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Security { /// /// Represents the currently authenticated diff --git a/src/Tgstation.Server.Host/Core/ICryptographySuite.cs b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs similarity index 95% rename from src/Tgstation.Server.Host/Core/ICryptographySuite.cs rename to src/Tgstation.Server.Host/Security/ICryptographySuite.cs index f773c17301..71685b1c10 100644 --- a/src/Tgstation.Server.Host/Core/ICryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs @@ -1,6 +1,6 @@ using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Security { /// /// Contains various cryptographic functions diff --git a/src/Tgstation.Server.Host/Core/ISystemIdentity.cs b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs similarity index 94% rename from src/Tgstation.Server.Host/Core/ISystemIdentity.cs rename to src/Tgstation.Server.Host/Security/ISystemIdentity.cs index 8901917d98..4ddf8f23fa 100644 --- a/src/Tgstation.Server.Host/Core/ISystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Security { /// /// Represents a user on the current diff --git a/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs similarity index 95% rename from src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs rename to src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs index a0de1cda52..1d5a4a53f5 100644 --- a/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs @@ -1,6 +1,6 @@ using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Security { /// /// Factory for s diff --git a/src/Tgstation.Server.Host/Core/ITokenManager.cs b/src/Tgstation.Server.Host/Security/ITokenManager.cs similarity index 96% rename from src/Tgstation.Server.Host/Core/ITokenManager.cs rename to src/Tgstation.Server.Host/Security/ITokenManager.cs index d34def21a2..bb94e38e48 100644 --- a/src/Tgstation.Server.Host/Core/ITokenManager.cs +++ b/src/Tgstation.Server.Host/Security/ITokenManager.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.Security { /// /// For creating and decoding JWTs From af808c5aa7a5e17ddc6218b1f74eaef3321e5216 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 11 Apr 2018 15:50:44 -0400 Subject: [PATCH 083/397] Moving on up --- .../ApplicationBuilderExtensions.cs | 22 ----- .../Configuration/GeneralConfiguration.cs | 18 ++++ src/Tgstation.Server.Host/Core/Application.cs | 38 ++++++- .../HttpContextExtensions.cs | 18 ---- .../Models/DatabaseSeeder.cs | 1 - .../MySqlDesignTimeDbContextFactory.cs | 2 +- .../SqlServerDesignTimeDbContextFactory.cs | 2 +- .../SqliteDesignTimeDbContextFactory.cs | 2 +- src/Tgstation.Server.Host/Models/User.cs | 8 -- .../Security/AuthenticationContext.cs | 98 +++---------------- .../Security/CryptographySuite.cs | 8 -- .../Security/IAuthenticationContext.cs | 29 +++--- .../Security/ICryptographySuite.cs | 6 -- .../Security/ISystemIdentity.cs | 10 ++ .../{ITokenManager.cs => ITokenFactory.cs} | 12 +-- .../Security/TokenFactory.cs | 50 ++++++++++ .../Tgstation.Server.Host.csproj | 6 ++ src/Tgstation.Server.Host/appsettings.json | 3 + 18 files changed, 156 insertions(+), 177 deletions(-) delete mode 100644 src/Tgstation.Server.Host/ApplicationBuilderExtensions.cs create mode 100644 src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs delete mode 100644 src/Tgstation.Server.Host/HttpContextExtensions.cs rename src/Tgstation.Server.Host/Security/{ITokenManager.cs => ITokenFactory.cs} (52%) create mode 100644 src/Tgstation.Server.Host/Security/TokenFactory.cs diff --git a/src/Tgstation.Server.Host/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/ApplicationBuilderExtensions.cs deleted file mode 100644 index 36eaa08627..0000000000 --- a/src/Tgstation.Server.Host/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Tgstation.Server.Host.Security; - -namespace Tgstation.Server.Host -{ - /// - /// Extension methods for - /// - static class ApplicationBuilderExtensions - { - /// - /// Apply the middleware - /// - /// The to add the middleware for - /// - public static IApplicationBuilder UseAuthentication(this IApplicationBuilder applicationBuilder) - { - AuthenticationContext.AddToPipeline(applicationBuilder); - return applicationBuilder; - } - } -} diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs new file mode 100644 index 0000000000..e914de955f --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -0,0 +1,18 @@ +namespace Tgstation.Server.Host.Configuration +{ + /// + /// General configuration options + /// + sealed class GeneralConfiguration + { + /// + /// The key for the the resides in + /// + public const string Section = "General"; + + /// + /// The string used to validate JWTs + /// + public string TokenSigningKey { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 1f196815c9..7da33a207b 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -5,8 +5,11 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; using System; using System.Globalization; +using System.Reflection; +using System.Text; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -55,6 +58,39 @@ namespace Tgstation.Server.Host.Core services.AddMvc(); services.AddOptions(); + + var signingKey = configuration.GetSection(GeneralConfiguration.Section).Get().TokenSigningKey; + + if (signingKey == "default") + throw new InvalidOperationException("Do not use the default signing key!"); + + const string scheme = "JwtBearer"; + services.AddAuthentication((options) => + { + options.DefaultAuthenticateScheme = scheme; + options.DefaultChallengeScheme = scheme; + }).AddJwtBearer(scheme, jwtBearerOptions => + { + jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)), + + ValidateIssuer = true, + ValidIssuer = Assembly.GetExecutingAssembly().GetName().Name, + + ValidateLifetime = true, + + ClockSkew = TimeSpan.FromMinutes(5), + + RequireSignedTokens = true, + + RequireExpirationTime = true, + + }; + + jwtBearerOptions.SaveToken = true; + }); var databaseConfiguration = databaseConfigurationSection.Get(); void ConfigureDatabase(DbContextOptionsBuilder builder) @@ -84,6 +120,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton, PasswordHasher>(); + services.AddSingleton(); } /// @@ -105,7 +142,6 @@ namespace Tgstation.Server.Host.Core }); applicationBuilder.UseAuthentication(); - applicationBuilder.UseMvc(); } } diff --git a/src/Tgstation.Server.Host/HttpContextExtensions.cs b/src/Tgstation.Server.Host/HttpContextExtensions.cs deleted file mode 100644 index e3677240e9..0000000000 --- a/src/Tgstation.Server.Host/HttpContextExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Tgstation.Server.Host.Security; - -namespace Tgstation.Server.Host -{ - /// - /// Extension methods for - /// - static class HttpContextExtensions - { - /// - /// Get the associated with the - /// - /// The containing the - /// The associated with the - public static IAuthenticationContext AuthenticationContext(this HttpContext httpContext) => Security.AuthenticationContext.Current(httpContext); - } -} diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index bd6236c940..7ccf4f33ca 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -41,7 +41,6 @@ namespace Tgstation.Server.Host.Models Name = "Admin", Enabled = true, }; - cryptographySuite.RegenerateUserToken(admin); cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); databaseContext.Users.Add(admin); diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs index 33640bd86f..b33b088ed2 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Logging; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs index 2b6f15c7cb..935768080b 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Logging; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs index 21087ac164..637bebfd0f 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Logging; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models.Migrations { diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index 1264476db3..edbd1ff2a5 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models { @@ -12,12 +10,6 @@ namespace Tgstation.Server.Host.Models /// public string PasswordHash { get; set; } - /// - /// The value used to encrypt JWTs - /// - [StringLength(CryptographySuite.SecureStringLength)] - public string TokenSecret { get; set; } - /// /// See /// diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index 3e9ac45993..d121e81bab 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -1,13 +1,5 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api; +using System; using Tgstation.Server.Host.Models; -using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Security { @@ -16,82 +8,27 @@ namespace Tgstation.Server.Host.Security /// sealed class AuthenticationContext : IAuthenticationContext { - /// - /// Used to store the in - /// - static readonly object contextKey = new object(); + /// + public User User { get; } - /// - /// Get the current from an - /// - /// The containing the - /// The in - public static IAuthenticationContext Current(HttpContext httpContext) => (IAuthenticationContext)httpContext.Items[contextKey]; - - /// - /// Adds handling to an pipeline - /// - /// The to add to - public static void AddToPipeline(IApplicationBuilder applicationBuilder) - { - applicationBuilder.Use(async (httpContext, next) => - { - ApiHeaders headers; - try - { - headers = new ApiHeaders(httpContext.Request.GetTypedHeaders()); - } - catch (InvalidOperationException) - { - httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - return; - } - - ISystemIdentity systemIdentity; - - var systemIdentityFactory = applicationBuilder.ApplicationServices.GetRequiredService(); - var tokenManager = applicationBuilder.ApplicationServices.GetRequiredService(); - var databaseContext = applicationBuilder.ApplicationServices.GetRequiredService(); - try - { - if (headers.IsTokenAuthentication) - { - var user = await tokenManager.GetUser(new Token { Value = headers.Token }, httpContext.RequestAborted).ConfigureAwait(false); - systemIdentity = systemIdentityFactory.CreateSystemIdentity(user); - } - else - systemIdentity = systemIdentityFactory.CreateSystemIdentity(headers.Username, headers.Password); - } - catch (UnauthorizedAccessException) - { - httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - return; - } - - using (var authContext = new AuthenticationContext(systemIdentity, databaseContext)) - { - httpContext.Items[contextKey] = authContext; - await next().ConfigureAwait(false); - } - }); - } + /// + public InstanceUser InstanceUser { get; } /// public ISystemIdentity SystemIdentity { get; } - /// - /// The for the - /// - readonly IDatabaseContext databaseContext; - /// /// Construct a /// /// The value of - /// The value of - public AuthenticationContext(ISystemIdentity systemIdentity, IDatabaseContext databaseContext) + /// The value of + /// The value of + public AuthenticationContext(ISystemIdentity systemIdentity, User user, InstanceUser instanceUser) { - this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + User = user ?? throw new ArgumentNullException(nameof(user)); + if (systemIdentity == null && User.SystemIdentifier != null) + throw new ArgumentNullException(nameof(systemIdentity)); + InstanceUser = instanceUser; SystemIdentity = systemIdentity; } @@ -99,15 +36,6 @@ namespace Tgstation.Server.Host.Security public void Dispose() => SystemIdentity.Dispose(); /// - public Task User(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public Task InstanceUser(Models.Instance instance, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public IAuthenticationContext Clone() => new AuthenticationContext(SystemIdentity.Clone(), User, InstanceUser); } } diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs index 7b04a11e4e..7fa35d77ee 100644 --- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -49,14 +49,6 @@ namespace Tgstation.Server.Host.Security /// The value of public CryptographySuite(IPasswordHasher passwordHasher) => this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); - /// - public void RegenerateUserToken(User user) - { - if (user == null) - throw new ArgumentNullException(nameof(user)); - user.TokenSecret = GenerateSecureString(); - } - /// public void SetUserPassword(User user, string newPassword) { diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs index 1b5ce33483..5766d51a44 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs @@ -1,6 +1,4 @@ using System; -using System.Threading; -using System.Threading.Tasks; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -11,23 +9,24 @@ namespace Tgstation.Server.Host.Security interface IAuthenticationContext : IDisposable { /// - /// The of + /// The authenticated user + /// + User User { get; } + + /// + /// The of if applicable + /// + InstanceUser InstanceUser { get; } + + /// + /// The of if applicable /// ISystemIdentity SystemIdentity { get; } /// - /// The represented by the + /// Creates a copy of the /// - /// The for the operation - /// A resulting in the represented by the - Task User(CancellationToken cancellationToken); - - /// - /// The represented by and a given - /// - /// The of the - /// The for the operation - /// A resulting in the represented by and a given - Task InstanceUser(Instance instance, CancellationToken cancellationToken); + /// A new + IAuthenticationContext Clone(); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs index 71685b1c10..21b3de1a7a 100644 --- a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs @@ -13,11 +13,5 @@ namespace Tgstation.Server.Host.Security /// The whos is to be set /// The new password for the void SetUserPassword(User user, string newPassword); - - /// - /// Sets a new for a givent - /// - /// The whos is to be set - void RegenerateUserToken(User user); } } diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs index 4ddf8f23fa..7d0196bdca 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Host.Security { @@ -28,5 +30,13 @@ namespace Tgstation.Server.Host.Security /// /// A new mirroring the current one ISystemIdentity Clone(); + + /// + /// Runs a given in the context of the + /// + /// The to perform, should be simple and not use any s or threading + /// The for the operation + /// A representing the running operation + Task RunImpersonated(Action action, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Security/ITokenManager.cs b/src/Tgstation.Server.Host/Security/ITokenFactory.cs similarity index 52% rename from src/Tgstation.Server.Host/Security/ITokenManager.cs rename to src/Tgstation.Server.Host/Security/ITokenFactory.cs index bb94e38e48..f7c05b6901 100644 --- a/src/Tgstation.Server.Host/Security/ITokenManager.cs +++ b/src/Tgstation.Server.Host/Security/ITokenFactory.cs @@ -5,18 +5,10 @@ using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Security { /// - /// For creating and decoding JWTs + /// For creating s /// - interface ITokenManager + interface ITokenFactory { - /// - /// Get the user associated with a - /// - /// The to get the user for - /// The for the operation - /// The associated with the - Task GetUser(Token token, CancellationToken cancellationToken); - /// /// Create a for a given /// diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs new file mode 100644 index 0000000000..628c78cb5f --- /dev/null +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Globalization; +using System.IdentityModel.Tokens.Jwt; +using System.Reflection; +using System.Security.Claims; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Configuration; + +namespace Tgstation.Server.Host.Security +{ + /// + sealed class TokenFactory : ITokenFactory + { + /// + /// The for the + /// + readonly GeneralConfiguration generalConfiguration; + + /// + /// Construct a + /// + /// The containing the value of + public TokenFactory(IOptions generalConfigurationOptions) => generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); + + /// + public Token CreateToken(Models.User user) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + + var claims = new Claim[] + { + new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString(CultureInfo.InvariantCulture)), + new Claim(JwtRegisteredClaimNames.Exp, $"{DateTimeOffset.Now.AddDays(1).ToUnixTimeSeconds()}"), + new Claim(JwtRegisteredClaimNames.Nbf, $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"), + new Claim(JwtRegisteredClaimNames.Iss, Assembly.GetExecutingAssembly().GetName().Name) + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(generalConfiguration.TokenSigningKey)); + + var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims)); + return new Token { Value = new JwtSecurityTokenHandler().WriteToken(token) }; + } + } +} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index acd5be3e36..dfd05a5c72 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -38,6 +38,7 @@ + @@ -46,6 +47,7 @@ + @@ -60,4 +62,8 @@ + + + + diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index bfcbeffa32..c2960461f8 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -17,6 +17,9 @@ } } }, + "General": { + "TokenSigningKey": "Default" + }, "Database": { "DatabaseType": "Sqlite", "ConnectionString": "Fake" From a9cc9b96feaa44757aefcf32236b2d7b5f5edb3f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 11 Apr 2018 16:44:55 -0400 Subject: [PATCH 084/397] Where am I... --- src/Tgstation.Server.Api/ApiHeaders.cs | 18 +++--- .../Controllers/ApiController.cs | 57 +++++++++++++++++++ .../Controllers/HomeController.cs | 14 +++++ .../Models/ChatChannel.cs | 2 +- .../Models/ChatSettings.cs | 2 +- .../Models/CompileJob.cs | 2 +- .../Models/DreamDaemonSettings.cs | 2 +- .../Models/DreamMakerSettings.cs | 2 +- .../Models/IDatabaseContext.cs | 2 +- src/Tgstation.Server.Host/Models/Instance.cs | 3 +- .../Models/InstanceUser.cs | 2 +- src/Tgstation.Server.Host/Models/Job.cs | 2 +- .../Models/RepositorySettings.cs | 2 +- .../Models/RevisionInformation.cs | 2 +- .../Models/ServerSettings.cs | 2 +- src/Tgstation.Server.Host/Models/TestMerge.cs | 2 +- src/Tgstation.Server.Host/Models/User.cs | 2 +- .../Security/IAuthenticationContext.cs | 2 +- .../Security/ISystemIdentity.cs | 2 +- .../Security/ITokenFactory.cs | 4 +- .../Security/TokenFactory.cs | 6 ++ .../Tgstation.Server.Host.csproj | 6 +- 22 files changed, 107 insertions(+), 31 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/ApiController.cs create mode 100644 src/Tgstation.Server.Host/Controllers/HomeController.cs diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 9e7563e3cb..436ae6c412 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -15,10 +15,15 @@ namespace Tgstation.Server.Api /// public sealed class ApiHeaders { + /// + /// TODO: Remove this when https://github.com/dotnet/corefx/pull/26701 makes it into the sdk + /// + public const string ApplicationJson = "application/json"; + /// /// The username header key /// - const string usernameHeader = "Username"; + const string usernameHeader = "Username"; /// /// The JWT authentication header scheme @@ -30,11 +35,6 @@ namespace Tgstation.Server.Api /// const string passwordAuthenticationScheme = "Password"; - /// - /// TODO: Remove this when https://github.com/dotnet/corefx/pull/26701 makes it into the sdk - /// - const string applicationJson = "application/json"; - /// /// The current /// @@ -105,9 +105,9 @@ namespace Tgstation.Server.Api /// The containing the public ApiHeaders(RequestHeaders requestHeaders) { - var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(applicationJson); + var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(ApplicationJson); if (!requestHeaders.Accept.Any(x => x == jsonAccept)) - throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", applicationJson)); + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", ApplicationJson)); if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out StringValues userAgentValues) || !ProductInfoHeaderValue.TryParse(userAgentValues.FirstOrDefault(), out ProductInfoHeaderValue clientUserAgent)) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} headers!", HeaderNames.UserAgent)); @@ -185,7 +185,7 @@ namespace Tgstation.Server.Api throw new ArgumentNullException(nameof(headers)); headers.Clear(); - headers.Accept.Add(new MediaTypeWithQualityHeaderValue(applicationJson)); + headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson)); if (IsTokenAuthentication) headers.Authorization = new AuthenticationHeaderValue(jwtAuthenticationScheme, Token); else diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs new file mode 100644 index 0000000000..b21f1edad4 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + [Authorize] + [Produces(ApiHeaders.ApplicationJson)] + [Consumes(ApiHeaders.ApplicationJson)] + public abstract class ApiController : Controller + { + protected ModelAttribute ModelAttribute => (ModelAttribute)typeof(TModel).GetCustomAttributes(typeof(ModelAttribute), true).First(); + + protected ApiHeaders ApiHeaders { get; } + + protected IDatabaseContext DatabaseContext { get; } + + protected IAuthenticationContext AuthenticationContext { get; private set; } + + readonly ITokenFactory tokenManager; + + public ApiController(IDatabaseContext databaseContext, ITokenFactory tokenManager) + { + DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + this.tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager)); + } + + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + User.Claims. + + await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); + } + + [HttpPut] + public virtual Task Create([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + + [HttpGet] + public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + + [HttpPost] + public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + + [HttpDelete] + public virtual Task Delete([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + + [HttpGet("/List")] + public virtual Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + } +} diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs new file mode 100644 index 0000000000..6b4cdc87f5 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; +using System.Reflection; +using Tgstation.Server.Api; + +namespace Tgstation.Server.Host.Controllers +{ + [Route("/")] + [Produces(ApiHeaders.ApplicationJson)] + public sealed class HomeController : Controller + { + [HttpGet] + public JsonResult Home() => Json(Assembly.GetExecutingAssembly().GetName().Version); + } +} diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index e091361f49..8f57e1e885 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class ChatChannel : Api.Models.ChatChannel + public sealed class ChatChannel : Api.Models.ChatChannel { /// /// The row Id diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs index f81f562fa5..5d3df56af5 100644 --- a/src/Tgstation.Server.Host/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Host.Models { /// - sealed class ChatSettings : Api.Models.Internal.ChatSettings + public sealed class ChatSettings : Api.Models.Internal.ChatSettings { /// /// The row Id diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 2b8404da46..29bdac135c 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class CompileJob : Api.Models.Internal.CompileJob + public sealed class CompileJob : Api.Models.Internal.CompileJob { /// /// See diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs index 33d0246b77..6a1a550a42 100644 --- a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class DreamDaemonSettings : Api.Models.Internal.DreamDaemonSettings + public sealed class DreamDaemonSettings : Api.Models.Internal.DreamDaemonSettings { /// /// The row Id diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index c6621a07f4..286be820a2 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings + public sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings { /// /// The row Id diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 9330d3d175..9ed9acf3a4 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Models /// /// Represents the database /// - interface IDatabaseContext + public interface IDatabaseContext { /// /// The s in the diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 59224b2edd..ca3a5bfad5 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Host.Models { /// /// Represents an in the database /// - sealed class Instance : Api.Models.Instance + public sealed class Instance : Api.Models.Instance { /// /// The for the diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index e2c343cbb1..7ccfc17ea0 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class InstanceUser : Api.Models.InstanceUser + public sealed class InstanceUser : Api.Models.InstanceUser { /// /// The row Id diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 005972d7d6..43d1fefba4 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class Job : Api.Models.Internal.Job + public sealed class Job : Api.Models.Internal.Job { /// /// See diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 5017df5343..77e529f2ec 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class RepositorySettings : Api.Models.Internal.RepositorySettings + public sealed class RepositorySettings : Api.Models.Internal.RepositorySettings { /// /// The row Id diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index dddd11bb77..057cc7e680 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class RevisionInformation : Api.Models.Internal.RevisionInformation + public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation { /// /// The row Id diff --git a/src/Tgstation.Server.Host/Models/ServerSettings.cs b/src/Tgstation.Server.Host/Models/ServerSettings.cs index df05f75182..4326d9f563 100644 --- a/src/Tgstation.Server.Host/Models/ServerSettings.cs +++ b/src/Tgstation.Server.Host/Models/ServerSettings.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class ServerSettings : Api.Models.Internal.ServerSettings + public sealed class ServerSettings : Api.Models.Internal.ServerSettings { /// /// The row Id diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index ad2eab8783..e72fc66e3c 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class TestMerge : Api.Models.Internal.TestMerge + public sealed class TestMerge : Api.Models.Internal.TestMerge { /// /// See diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index edbd1ff2a5..dec14852f9 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Models { /// - sealed class User : Api.Models.Internal.User + public sealed class User : Api.Models.Internal.User { /// /// The hash of the user's password diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs index 5766d51a44..e50a3c5467 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Security /// /// Represents the currently authenticated /// - interface IAuthenticationContext : IDisposable + public interface IAuthenticationContext : IDisposable { /// /// The authenticated user diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs index 7d0196bdca..ff2dc3482c 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Security /// /// Represents a user on the current /// - interface ISystemIdentity : IDisposable + public interface ISystemIdentity : IDisposable { /// /// A unique identifier for the user diff --git a/src/Tgstation.Server.Host/Security/ITokenFactory.cs b/src/Tgstation.Server.Host/Security/ITokenFactory.cs index f7c05b6901..fdd8949ef6 100644 --- a/src/Tgstation.Server.Host/Security/ITokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/ITokenFactory.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Security /// /// For creating s /// - interface ITokenFactory + public interface ITokenFactory { /// /// Create a for a given @@ -15,5 +15,7 @@ namespace Tgstation.Server.Host.Security /// The to create the token for. Must have the and fields available /// A new Token CreateToken(Models.User user); + + Task GetUser(Token token, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 628c78cb5f..3db336e859 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -46,5 +46,11 @@ namespace Tgstation.Server.Host.Security var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims)); return new Token { Value = new JwtSecurityTokenHandler().WriteToken(token) }; } + + /// + public Task GetUser(Token token, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index dfd05a5c72..a2125b38fb 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -10,10 +10,12 @@ true bin\Release\netstandard2.0\Tgstation.Server.Host.xml + 1701;1702;1705;CA2227 latest + 1701;1702;1705;CA2227 @@ -62,8 +64,4 @@ - - - - From 0d5c4c8e4891ddc7101620145eb5d196beb08cb6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 12 Apr 2018 13:25:03 -0400 Subject: [PATCH 085/397] Exploratory architecture best architecture --- src/Tgstation.Server.Api/ApiHeaders.cs | 24 +++++++- .../Controllers/ApiController.cs | 25 ++++---- src/Tgstation.Server.Host/Core/Application.cs | 52 ++++++++++++++++- .../Models/InstanceUser.cs | 10 +++- .../Security/AuthenticationContext.cs | 10 +++- .../Security/AuthenticationContextFactory.cs | 57 +++++++++++++++++++ .../Security/IAuthenticationContext.cs | 3 + .../Security/IAuthenticationContextFactory.cs | 15 +++++ .../Security/ISystemIdentityFactory.cs | 2 +- .../Security/ITokenFactory.cs | 2 - .../Security/TokenFactory.cs | 6 -- 11 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs create mode 100644 src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 436ae6c412..aedfa82fe2 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Http; -using System; +using System; using System.Globalization; using System.Linq; using System.Net.Http.Headers; @@ -21,10 +20,15 @@ namespace Tgstation.Server.Api public const string ApplicationJson = "application/json"; /// - /// The username header key + /// The header key /// const string usernameHeader = "Username"; + /// + /// The header key + /// + const string instanceIdHeader = "InstanceId"; + /// /// The JWT authentication header scheme /// @@ -40,6 +44,11 @@ namespace Tgstation.Server.Api /// static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName(); + /// + /// The being accessed + /// + public long? InstanceId { get; set; } + /// /// The client's user agent /// @@ -139,6 +148,13 @@ namespace Tgstation.Server.Api if (String.IsNullOrEmpty(parameter)) throw new InvalidOperationException("Missing authentication parameter!"); + if(requestHeaders.Headers.TryGetValue(instanceIdHeader, out StringValues instanceIdValues)) + { + var instanceIdString = instanceIdValues.FirstOrDefault(); + if (instanceIdString != default && Int64.TryParse(instanceIdString, out long instanceId)) + InstanceId = instanceId; + } + switch (scheme) { case jwtAuthenticationScheme: @@ -195,6 +211,8 @@ namespace Tgstation.Server.Api } headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent)); headers.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue(assemblyName.Name, assemblyName.Version.ToString()))); + if(InstanceId.HasValue) + headers.Add(instanceIdHeader, InstanceId.ToString()); } } } diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index b21f1edad4..eccde8cde1 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -1,6 +1,5 @@ -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading; @@ -12,6 +11,7 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { [Authorize] + [Route("[controller]")] [Produces(ApiHeaders.ApplicationJson)] [Consumes(ApiHeaders.ApplicationJson)] public abstract class ApiController : Controller @@ -22,23 +22,22 @@ namespace Tgstation.Server.Host.Controllers protected IDatabaseContext DatabaseContext { get; } - protected IAuthenticationContext AuthenticationContext { get; private set; } + protected IAuthenticationContext AuthenticationContext { get; } - readonly ITokenFactory tokenManager; + protected Instance Instance { get; } - public ApiController(IDatabaseContext databaseContext, ITokenFactory tokenManager) + public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) { DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); - this.tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager)); - } + if (authenticationContextFactory == null) + throw new ArgumentNullException(nameof(authenticationContextFactory)); - public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) - { - User.Claims. - - await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); - } + AuthenticationContext = authenticationContextFactory.CurrentAuthenticationContext; + if (AuthenticationContext.InstanceUser != null) + Instance = AuthenticationContext.InstanceUser.Instance; + } + [HttpPut] public virtual Task Create([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 7da33a207b..b73932a36b 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,15 +1,23 @@ using Cyberboss.AspNetCore.AsyncInitializer; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using System; +using System.Collections.Generic; using System.Globalization; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; using System.Reflection; +using System.Security.Claims; using System.Text; +using Tgstation.Server.Api; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -85,11 +93,49 @@ namespace Tgstation.Server.Host.Core RequireSignedTokens = true, - RequireExpirationTime = true, - + RequireExpirationTime = true }; + jwtBearerOptions.Events = new JwtBearerEvents + { + OnTokenValidated = async context => + { + var databaseContext = context.HttpContext.RequestServices.GetRequiredService(); + var authenticationContextFactory = context.HttpContext.RequestServices.GetRequiredService(); - jwtBearerOptions.SaveToken = true; + var userIdClaim = context.Principal.Claims.Where(x => x.Properties.ContainsKey(JwtRegisteredClaimNames.NameId)).FirstOrDefault(); + + if (userIdClaim == default(Claim)) + throw new InvalidOperationException("Missing required claim!"); + + long userId; + try + { + userId = Int64.Parse(userIdClaim.Value, CultureInfo.InvariantCulture); + } + catch (Exception e) + { + throw new InvalidOperationException("Failed to parse user ID!", e); + } + + var requestHeaders = context.HttpContext.Request.GetTypedHeaders(); + + var apiHeaders = new ApiHeaders(requestHeaders); + + await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.HttpContext.RequestAborted).ConfigureAwait(false); + + var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; + + var enumerator = Enum.GetValues(typeof(RightsType)); + var claims = new List + { + Capacity = enumerator.Length + }; + foreach (RightsType I in enumerator) + claims.Add(new Claim(I.ToString(), authenticationContext.GetRight(I).ToString(CultureInfo.InvariantCulture))); + + context.Principal.AddIdentity(new ClaimsIdentity(claims)); + } + }; }); var databaseConfiguration = databaseConfigurationSection.Get(); diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 7ccfc17ea0..6566b2c891 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Host.Models +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models { /// public sealed class InstanceUser : Api.Models.InstanceUser @@ -7,5 +9,11 @@ /// The row Id /// public long Id { get; set; } + + /// + /// The the belongs to + /// + [Required] + public Instance Instance { get; set; } } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index d121e81bab..f11783dba1 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -1,4 +1,6 @@ -using System; +using Microsoft.AspNetCore.Http; +using System; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -37,5 +39,11 @@ namespace Tgstation.Server.Host.Security /// public IAuthenticationContext Clone() => new AuthenticationContext(SystemIdentity.Clone(), User, InstanceUser); + + /// + public long GetRight(RightsType rightsType) + { + throw new NotImplementedException(); + } } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs new file mode 100644 index 0000000000..f20835b583 --- /dev/null +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Security +{ + /// + sealed class AuthenticationContextFactory : IAuthenticationContextFactory + { + /// + public IAuthenticationContext CurrentAuthenticationContext { get; private set; } + + /// + /// The for the + /// + readonly ISystemIdentityFactory systemIdentityFactory; + /// + /// The for the + /// + readonly IDatabaseContext databaseContext; + + /// + /// Construct an + /// + /// The value of + /// The value of + public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContext databaseContext) + { + this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); + this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + } + + /// + public async Task CreateAuthenticationContext(long userId, long? instanceId, CancellationToken cancellationToken) + { + if (CurrentAuthenticationContext != null) + throw new InvalidOperationException("Authentication context has already been loaded"); + + var userQuery = databaseContext.Users.Where(x => x.Id == userId); + + if (instanceId.HasValue) + userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId)); + + var user = await userQuery.FirstAsync(cancellationToken).ConfigureAwait(false); + + InstanceUser instanceUser = null; + if (instanceId.HasValue) + instanceUser = user.InstanceUsers.First(); + + CurrentAuthenticationContext = new AuthenticationContext(systemIdentityFactory.CreateSystemIdentity(user), user, instanceUser); + } + } +} diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs index e50a3c5467..724cdf9921 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs @@ -1,4 +1,5 @@ using System; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -18,6 +19,8 @@ namespace Tgstation.Server.Host.Security /// InstanceUser InstanceUser { get; } + long GetRight(RightsType rightsType); + /// /// The of if applicable /// diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs new file mode 100644 index 0000000000..4051035c0c --- /dev/null +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs @@ -0,0 +1,15 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Security +{ + /// + /// For creating and accessing authentication contexts + /// + public interface IAuthenticationContextFactory + { + IAuthenticationContext CurrentAuthenticationContext { get; } + + Task CreateAuthenticationContext(long userId, long? instanceId, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs index 1d5a4a53f5..d04c8d6d28 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Security /// Create a for a given /// /// The user to create a for - /// A new + /// A new or if the has no ISystemIdentity CreateSystemIdentity(User user); /// diff --git a/src/Tgstation.Server.Host/Security/ITokenFactory.cs b/src/Tgstation.Server.Host/Security/ITokenFactory.cs index fdd8949ef6..0a272438ea 100644 --- a/src/Tgstation.Server.Host/Security/ITokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/ITokenFactory.cs @@ -15,7 +15,5 @@ namespace Tgstation.Server.Host.Security /// The to create the token for. Must have the and fields available /// A new Token CreateToken(Models.User user); - - Task GetUser(Token token, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 3db336e859..628c78cb5f 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -46,11 +46,5 @@ namespace Tgstation.Server.Host.Security var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims)); return new Token { Value = new JwtSecurityTokenHandler().WriteToken(token) }; } - - /// - public Task GetUser(Token token, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } } } From 92cb9992f5e883851f12a28f51a4cc28e1c83966 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Apr 2018 15:07:06 -0400 Subject: [PATCH 086/397] Working token authentication --- src/Tgstation.Server.Api/ApiHeaders.cs | 16 +-- src/Tgstation.Server.Api/Models/Token.cs | 2 +- .../Rights/RightsHelper.cs | 26 ++++- .../IServerClientFactory.cs | 2 +- .../Controllers/ApiController.cs | 99 ++++++++++++++----- .../Controllers/HomeController.cs | 80 ++++++++++++++- .../Controllers/ModelController.cs | 32 ++++++ src/Tgstation.Server.Host/Core/Application.cs | 58 +++-------- .../Models/DatabaseContext.cs | 3 +- .../Security/AuthenticationContext.cs | 20 +++- .../Security/AuthenticationContextFactory.cs | 2 +- .../Security/IAuthenticationContext.cs | 2 +- .../Security/ISystemIdentityFactory.cs | 2 +- .../Security/ITokenFactory.cs | 2 +- .../Security/SystemIdentityFactory.cs | 21 ++++ .../Security/TokenFactory.cs | 12 ++- .../Security/TestAuthenticationContext.cs | 53 ++++++++++ 17 files changed, 337 insertions(+), 95 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/ModelController.cs create mode 100644 src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs create mode 100644 tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index aedfa82fe2..d0a10ed87b 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net.Http.Headers; @@ -32,12 +33,12 @@ namespace Tgstation.Server.Api /// /// The JWT authentication header scheme /// - const string jwtAuthenticationScheme = "Bearer"; + const string jwtAuthenticationScheme = "bearer"; /// /// The password authentication header scheme /// - const string passwordAuthenticationScheme = "Password"; + const string passwordAuthenticationScheme = "password"; /// /// The current @@ -115,7 +116,7 @@ namespace Tgstation.Server.Api public ApiHeaders(RequestHeaders requestHeaders) { var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(ApplicationJson); - if (!requestHeaders.Accept.Any(x => x == jsonAccept)) + if (!requestHeaders.Accept.Any(x => x.MediaType == jsonAccept.MediaType)) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", ApplicationJson)); if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out StringValues userAgentValues) || !ProductInfoHeaderValue.TryParse(userAgentValues.FirstOrDefault(), out ProductInfoHeaderValue clientUserAgent)) @@ -140,11 +141,14 @@ namespace Tgstation.Server.Api if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization)) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization)); - var scheme = authorization.First(); - + var auth = authorization.First(); + var splits = new List(auth.Split(' ')); + var scheme = splits.First(); if (String.IsNullOrWhiteSpace(scheme)) throw new InvalidOperationException("Missing authentication scheme!"); - var parameter = authorization[1]; + + splits.RemoveAt(0); + var parameter = String.Concat(splits); if (String.IsNullOrEmpty(parameter)) throw new InvalidOperationException("Missing authentication parameter!"); diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index 5f75d2ff03..9ec9a5edd5 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -8,6 +8,6 @@ /// /// The value of the JWT /// - public string Value { get; set; } + public string Bearer { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index 0f754c4d13..9ccbf29e22 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -25,11 +25,35 @@ namespace Tgstation.Server.Api.Rights { RightsType.InstanceUser, typeof(InstanceUserRights) } }; + static readonly IReadOnlyDictionary rightMap = CreateRightsMap(); + + static IReadOnlyDictionary CreateRightsMap() + { + var dic = new Dictionary(); + foreach (var I in typeMap) + dic.Add(I.Value, I.Key); + return dic; + } + /// /// Map a given to its respective /// /// The to lookup /// The of the given - public static Type TypeToRight(RightsType rightsType) => typeMap[rightsType]; + public static Type RightToType(RightsType rightsType) => typeMap[rightsType]; + + public static string RoleName(TRight right) => String.Concat(typeof(TRight).Name, '.', right.ToString()); + public static string RoleName(RightsType rightsType, int right) + { + var enumType = typeMap[rightsType]; + return String.Concat(enumType.Name, '.', Enum.GetName(enumType, right)); + } + + /// + /// Check if a given is meant for an + /// + /// The to check + /// if is an instance right, otherwise + public static bool IsInstanceRight(RightsType rightsType) => !(rightsType == RightsType.Administration || rightsType == RightsType.InstanceManager); } } diff --git a/src/Tgstation.Server.Client/IServerClientFactory.cs b/src/Tgstation.Server.Client/IServerClientFactory.cs index fb79446eb0..b3b3ecb506 100644 --- a/src/Tgstation.Server.Client/IServerClientFactory.cs +++ b/src/Tgstation.Server.Client/IServerClientFactory.cs @@ -24,7 +24,7 @@ namespace Tgstation.Server.Client /// Create a /// /// The URL to access tgstation-server at - /// The to access the API with + /// The to access the API with /// The initial timeout for the connection /// The for the operation /// A resulting in a new diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index eccde8cde1..be7d20f51f 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -1,56 +1,103 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; using System; +using System.Collections.Generic; +using System.Globalization; +using System.IdentityModel.Tokens.Jwt; using System.Linq; -using System.Threading; +using System.Security.Claims; using System.Threading.Tasks; using Tgstation.Server.Api; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { - [Authorize] - [Route("[controller]")] [Produces(ApiHeaders.ApplicationJson)] [Consumes(ApiHeaders.ApplicationJson)] - public abstract class ApiController : Controller + public abstract class ApiController : Controller { - protected ModelAttribute ModelAttribute => (ModelAttribute)typeof(TModel).GetCustomAttributes(typeof(ModelAttribute), true).First(); - - protected ApiHeaders ApiHeaders { get; } + protected ApiHeaders ApiHeaders { get; private set; } protected IDatabaseContext DatabaseContext { get; } - protected IAuthenticationContext AuthenticationContext { get; } + protected IAuthenticationContext AuthenticationContext { get; } protected Instance Instance { get; } + public static async Task OnTokenValidated(TokenValidatedContext context) + { + var databaseContext = context.HttpContext.RequestServices.GetRequiredService(); + var authenticationContextFactory = context.HttpContext.RequestServices.GetRequiredService(); + + var userIdClaim = context.Principal.FindFirst(JwtRegisteredClaimNames.Sub); + + if (userIdClaim == default(Claim)) + throw new InvalidOperationException("Missing required claim!"); + + long userId; + try + { + userId = Int64.Parse(userIdClaim.Value, CultureInfo.InvariantCulture); + } + catch (Exception e) + { + throw new InvalidOperationException("Failed to parse user ID!", e); + } + + ApiHeaders apiHeaders; + try + { + apiHeaders = new ApiHeaders(context.HttpContext.Request.GetTypedHeaders()); + } + catch + { + //let OnActionExecutionAsync handle the reponse + return; + } + + await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.HttpContext.RequestAborted).ConfigureAwait(false); + + var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; + + var enumerator = Enum.GetValues(typeof(RightsType)); + var claims = new List + { + Capacity = enumerator.Length + }; + foreach (RightsType I in enumerator) + claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, authenticationContext.GetRight(I)))); + + context.Principal.AddIdentity(new ClaimsIdentity(claims)); + } + public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) { DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); if (authenticationContextFactory == null) throw new ArgumentNullException(nameof(authenticationContextFactory)); - AuthenticationContext = authenticationContextFactory.CurrentAuthenticationContext; - - if (AuthenticationContext.InstanceUser != null) - Instance = AuthenticationContext.InstanceUser.Instance; + Instance = AuthenticationContext?.InstanceUser?.Instance; } - [HttpPut] - public virtual Task Create([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); - - [HttpGet] - public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + //validate the headers + try + { + ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); + } + catch (InvalidOperationException e) + { + await BadRequest(new { message = e.Message }).ExecuteResultAsync(context).ConfigureAwait(false); + return; + } - [HttpPost] - public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); - - [HttpDelete] - public virtual Task Delete([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); - - [HttpGet("/List")] - public virtual Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 6b4cdc87f5..58dec0d80d 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -1,14 +1,88 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; using System.Reflection; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Api; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { [Route("/")] - [Produces(ApiHeaders.ApplicationJson)] - public sealed class HomeController : Controller + public sealed class HomeController : ApiController { + /// + /// The for the + /// + readonly ITokenFactory tokenFactory; + /// + /// The for the + /// + readonly ISystemIdentityFactory systemIdentityFactory; + /// + /// The for the + /// + readonly IPasswordHasher passwordHasher; + + public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, IPasswordHasher passwordHasher) : base(databaseContext, authenticationContextFactory) + { + this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); + this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); + this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); + } + + [Authorize] [HttpGet] public JsonResult Home() => Json(Assembly.GetExecutingAssembly().GetName().Version); + + [HttpPost] + public async Task CreateToken(CancellationToken cancellationToken) + { + if (ApiHeaders.IsTokenAuthentication) + return BadRequest(new { message = "Cannot create a token using another token!" }); + + var user = await DatabaseContext.Users.Where(x => x.Name == ApiHeaders.Username).Select(x => new User{ + Id = x.Id, + PasswordHash = x.PasswordHash, + SystemIdentifier = x.SystemIdentifier, + Enabled = x.Enabled + }).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + if (user == null) + return Unauthorized(); + + if(user.PasswordHash != null) + { + var hashResult = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, ApiHeaders.Password); + switch (hashResult) + { + case PasswordVerificationResult.Failed: + return Unauthorized(); + case PasswordVerificationResult.SuccessRehashNeeded: + user.PasswordHash = passwordHasher.HashPassword(user, ApiHeaders.Password); + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + break; + } + } + else + try + { + var systemIdentity = systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password); + } + catch + { + return Unauthorized(); + } + + if (!user.Enabled) + return Forbid(); + + return Json(tokenFactory.CreateToken(user)); + } } } diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs new file mode 100644 index 0000000000..c97e498c3c --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + public abstract class ModelController : ApiController + { + protected static readonly ModelAttribute ModelAttribute = (ModelAttribute)typeof(TModel).GetCustomAttributes(typeof(ModelAttribute), true).First(); + + public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory) { } + + [HttpPut] + public virtual Task Create([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + + [HttpGet] + public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + + [HttpPost] + public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + + [HttpDelete] + public virtual Task Delete([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + + [HttpGet("/List")] + public virtual Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b73932a36b..53a3e1639b 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -19,6 +19,7 @@ using System.Text; using Tgstation.Server.Api; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Controllers; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -63,11 +64,13 @@ namespace Tgstation.Server.Host.Core var workingDir = Environment.CurrentDirectory; var databaseConfigurationSection = configuration.GetSection(DatabaseConfiguration.Section); services.Configure(databaseConfigurationSection); + var generalConfigSection = configuration.GetSection(GeneralConfiguration.Section); + services.Configure(generalConfigSection); - services.AddMvc(); + services.AddMvc(); services.AddOptions(); - var signingKey = configuration.GetSection(GeneralConfiguration.Section).Get().TokenSigningKey; + var signingKey = generalConfigSection.Get().TokenSigningKey; if (signingKey == "default") throw new InvalidOperationException("Do not use the default signing key!"); @@ -85,9 +88,11 @@ namespace Tgstation.Server.Host.Core IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)), ValidateIssuer = true, - ValidIssuer = Assembly.GetExecutingAssembly().GetName().Name, + ValidIssuer = TokenFactory.TokenIssuer, ValidateLifetime = true, + ValidateAudience = true, + ValidAudience = TokenFactory.TokenAudience, ClockSkew = TimeSpan.FromMinutes(5), @@ -97,47 +102,11 @@ namespace Tgstation.Server.Host.Core }; jwtBearerOptions.Events = new JwtBearerEvents { - OnTokenValidated = async context => - { - var databaseContext = context.HttpContext.RequestServices.GetRequiredService(); - var authenticationContextFactory = context.HttpContext.RequestServices.GetRequiredService(); - - var userIdClaim = context.Principal.Claims.Where(x => x.Properties.ContainsKey(JwtRegisteredClaimNames.NameId)).FirstOrDefault(); - - if (userIdClaim == default(Claim)) - throw new InvalidOperationException("Missing required claim!"); - - long userId; - try - { - userId = Int64.Parse(userIdClaim.Value, CultureInfo.InvariantCulture); - } - catch (Exception e) - { - throw new InvalidOperationException("Failed to parse user ID!", e); - } - - var requestHeaders = context.HttpContext.Request.GetTypedHeaders(); - - var apiHeaders = new ApiHeaders(requestHeaders); - - await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.HttpContext.RequestAborted).ConfigureAwait(false); - - var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; - - var enumerator = Enum.GetValues(typeof(RightsType)); - var claims = new List - { - Capacity = enumerator.Length - }; - foreach (RightsType I in enumerator) - claims.Add(new Claim(I.ToString(), authenticationContext.GetRight(I).ToString(CultureInfo.InvariantCulture))); - - context.Principal.AddIdentity(new ClaimsIdentity(claims)); - } + OnTokenValidated = ApiController.OnTokenValidated }; }); - + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs + var databaseConfiguration = databaseConfigurationSection.Get(); void ConfigureDatabase(DbContextOptionsBuilder builder) { @@ -162,11 +131,14 @@ namespace Tgstation.Server.Host.Core default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}!", nameof(DatabaseType))); } - + + services.AddScoped(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton, PasswordHasher>(); services.AddSingleton(); + services.AddSingleton(); } /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index d0738df579..4dc62319ba 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -143,12 +143,13 @@ namespace Tgstation.Server.Host.Models { #if DEBUG await Database.EnsureCreatedAsync().ConfigureAwait(false); + var wasEmpty = (await Users.CountAsync().ConfigureAwait(false)) == 0; #else var migrations = await Database.GetAppliedMigrationsAsync().ConfigureAwait(false); var wasEmpty = !migrations.Any(); await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); - if (wasEmpty) #endif + if (wasEmpty) await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index f11783dba1..dab8b2e60a 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -1,5 +1,5 @@ -using Microsoft.AspNetCore.Http; -using System; +using System; +using System.Linq; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; @@ -41,9 +41,21 @@ namespace Tgstation.Server.Host.Security public IAuthenticationContext Clone() => new AuthenticationContext(SystemIdentity.Clone(), User, InstanceUser); /// - public long GetRight(RightsType rightsType) + public int GetRight(RightsType rightsType) { - throw new NotImplementedException(); + var isInstance = RightsHelper.IsInstanceRight(rightsType); + + if (isInstance && InstanceUser == null) + return 0; + var rightsEnum = RightsHelper.RightToType(rightsType); + // use the api versions because they're the ones that contain the actual properties + var typeToCheck = isInstance ? typeof(InstanceUser) : typeof(User); + + var prop = typeToCheck.GetProperties().Where(x => x.PropertyType == rightsEnum).First(); + + var right = prop.GetMethod.Invoke(isInstance ? (object)InstanceUser : User, Array.Empty()); + + return (int)right; } } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index f20835b583..7994ea69b6 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -51,7 +51,7 @@ namespace Tgstation.Server.Host.Security if (instanceId.HasValue) instanceUser = user.InstanceUsers.First(); - CurrentAuthenticationContext = new AuthenticationContext(systemIdentityFactory.CreateSystemIdentity(user), user, instanceUser); + CurrentAuthenticationContext = new AuthenticationContext(user.SystemIdentifier != null ? systemIdentityFactory.CreateSystemIdentity(user) : null, user, instanceUser); } } } diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs index 724cdf9921..a5af46a902 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs @@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Security /// InstanceUser InstanceUser { get; } - long GetRight(RightsType rightsType); + int GetRight(RightsType rightsType); /// /// The of if applicable diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs index d04c8d6d28..86f4f9b7e2 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Security /// /// Factory for s /// - interface ISystemIdentityFactory + public interface ISystemIdentityFactory { /// /// Create a for a given diff --git a/src/Tgstation.Server.Host/Security/ITokenFactory.cs b/src/Tgstation.Server.Host/Security/ITokenFactory.cs index 0a272438ea..77f8a1518c 100644 --- a/src/Tgstation.Server.Host/Security/ITokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/ITokenFactory.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Security /// /// Create a for a given /// - /// The to create the token for. Must have the and fields available + /// The to create the token for. Must have the field available /// A new Token CreateToken(Models.User user); } diff --git a/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs new file mode 100644 index 0000000000..f9e9c15a87 --- /dev/null +++ b/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs @@ -0,0 +1,21 @@ +using System; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Security +{ + /// + sealed class SystemIdentityFactory : ISystemIdentityFactory + { + /// + public ISystemIdentity CreateSystemIdentity(User user) + { + throw new NotImplementedException(); + } + + /// + public ISystemIdentity CreateSystemIdentity(string username, string password) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 628c78cb5f..174f5858ee 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -6,8 +6,6 @@ using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Security.Claims; using System.Text; -using System.Threading; -using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Configuration; @@ -16,6 +14,9 @@ namespace Tgstation.Server.Host.Security /// sealed class TokenFactory : ITokenFactory { + public static readonly string TokenAudience = typeof(Token).Assembly.GetName().Name; + public static readonly string TokenIssuer = Assembly.GetExecutingAssembly().GetName().Name; + /// /// The for the /// @@ -36,15 +37,16 @@ namespace Tgstation.Server.Host.Security var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString(CultureInfo.InvariantCulture)), - new Claim(JwtRegisteredClaimNames.Exp, $"{DateTimeOffset.Now.AddDays(1).ToUnixTimeSeconds()}"), + new Claim(JwtRegisteredClaimNames.Exp, $"{DateTimeOffset.Now.AddHours(1).ToUnixTimeSeconds()}"), new Claim(JwtRegisteredClaimNames.Nbf, $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"), - new Claim(JwtRegisteredClaimNames.Iss, Assembly.GetExecutingAssembly().GetName().Name) + new Claim(JwtRegisteredClaimNames.Iss, TokenIssuer), + new Claim(JwtRegisteredClaimNames.Aud, TokenAudience) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(generalConfiguration.TokenSigningKey)); var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims)); - return new Token { Value = new JwtSecurityTokenHandler().WriteToken(token) }; + return new Token { Bearer = new JwtSecurityTokenHandler().WriteToken(token) }; } } } diff --git a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs new file mode 100644 index 0000000000..980faf020a --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Security.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestAuthenticationContext + { + [TestMethod] + public void TestConstruction() + { + Assert.ThrowsException(() => new AuthenticationContext(null, null, null)); + var mockSystemIdentity = new Mock(); + + var user = new User(); + + var authContext = new AuthenticationContext(null, user, null); + Assert.ThrowsException(() => new AuthenticationContext(mockSystemIdentity.Object, null, null)); + + var instanceUser = new InstanceUser(); + + Assert.ThrowsException(() => new AuthenticationContext(null, null, instanceUser)); + Assert.ThrowsException(() => new AuthenticationContext(mockSystemIdentity.Object, null, instanceUser)); + authContext = new AuthenticationContext(mockSystemIdentity.Object, user, null); + authContext = new AuthenticationContext(null, user, instanceUser); + authContext = new AuthenticationContext(mockSystemIdentity.Object, user, instanceUser); + user.SystemIdentifier = "root"; + Assert.ThrowsException(() => new AuthenticationContext(null, user, null)); + } + + + [TestMethod] + public void TestGetRightsGeneric() + { + var user = new User(); + var instanceUser = new InstanceUser(); + var authContext = new AuthenticationContext(null, user, instanceUser); + + user.AdministrationRights = AdministrationRights.EditUsers; + instanceUser.ByondRights = ByondRights.ChangeVersion | ByondRights.ReadInstalled; + Assert.AreEqual((int)user.AdministrationRights, authContext.GetRight(RightsType.Administration)); + Assert.AreEqual((int)user.InstanceManagerRights, authContext.GetRight(RightsType.InstanceManager)); + Assert.AreEqual((int)instanceUser.ByondRights, authContext.GetRight(RightsType.Byond)); + Assert.AreEqual((int)instanceUser.RepositoryRights, authContext.GetRight(RightsType.Repository)); + } + } +} From 41b738debb4e4698d6a9633ddbc20578b7a3a122 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Apr 2018 15:42:05 -0400 Subject: [PATCH 087/397] Fix release build, generate token signing key each run --- src/Tgstation.Server.Api/ApiHeaders.cs | 2 +- .../Rights/RightsHelper.cs | 23 +++++---- .../Configuration/GeneralConfiguration.cs | 18 ------- .../Controllers/ApiController.cs | 26 ++++++++++ .../Controllers/HomeController.cs | 48 +++++++++++++------ .../Controllers/ModelController.cs | 42 +++++++++++++++- src/Tgstation.Server.Host/Core/Application.cs | 30 +++++------- .../Security/AuthenticationContextFactory.cs | 4 +- .../Security/CryptographySuite.cs | 42 ++++++++-------- .../Security/IAuthenticationContext.cs | 5 ++ .../Security/IAuthenticationContextFactory.cs | 10 ++++ .../Security/ICryptographySuite.cs | 10 +++- .../Security/TokenFactory.cs | 18 ++----- .../Tgstation.Server.Host.csproj | 4 -- src/Tgstation.Server.Host/appsettings.json | 3 -- 15 files changed, 174 insertions(+), 111 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index d0a10ed87b..0235c5d401 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -110,7 +110,7 @@ namespace Tgstation.Server.Api } /// - /// Construct and validates from a + /// Construct and validates from a set of /// /// The containing the public ApiHeaders(RequestHeaders requestHeaders) diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index 9ccbf29e22..2b3e0ddbc0 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -25,16 +25,6 @@ namespace Tgstation.Server.Api.Rights { RightsType.InstanceUser, typeof(InstanceUserRights) } }; - static readonly IReadOnlyDictionary rightMap = CreateRightsMap(); - - static IReadOnlyDictionary CreateRightsMap() - { - var dic = new Dictionary(); - foreach (var I in typeMap) - dic.Add(I.Value, I.Key); - return dic; - } - /// /// Map a given to its respective /// @@ -42,7 +32,20 @@ namespace Tgstation.Server.Api.Rights /// The of the given public static Type RightToType(RightsType rightsType) => typeMap[rightsType]; + /// + /// Gets the role claim name used for a given + /// + /// The + /// The + /// A representing the claim role name public static string RoleName(TRight right) => String.Concat(typeof(TRight).Name, '.', right.ToString()); + + /// + /// Gets the role claim name used for a given and + /// + /// The + /// The right value + /// A representing the claim role name public static string RoleName(RightsType rightsType, int right) { var enumType = typeMap[rightsType]; diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs deleted file mode 100644 index e914de955f..0000000000 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Tgstation.Server.Host.Configuration -{ - /// - /// General configuration options - /// - sealed class GeneralConfiguration - { - /// - /// The key for the the resides in - /// - public const string Section = "General"; - - /// - /// The string used to validate JWTs - /// - public string TokenSigningKey { get; set; } - } -} diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index be7d20f51f..2df14a9d56 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -17,18 +17,38 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { + /// + /// A for API functions + /// [Produces(ApiHeaders.ApplicationJson)] [Consumes(ApiHeaders.ApplicationJson)] public abstract class ApiController : Controller { + /// + /// The for the operation + /// protected ApiHeaders ApiHeaders { get; private set; } + /// + /// The for the operation + /// protected IDatabaseContext DatabaseContext { get; } + /// + /// The for the operation + /// protected IAuthenticationContext AuthenticationContext { get; } + /// + /// The for the operation + /// protected Instance Instance { get; } + /// + /// Runs after a has been validated. Creates the for the + /// + /// The for the operation + /// A representing the running operation public static async Task OnTokenValidated(TokenValidatedContext context) { var databaseContext = context.HttpContext.RequestServices.GetRequiredService(); @@ -75,6 +95,11 @@ namespace Tgstation.Server.Host.Controllers context.Principal.AddIdentity(new ClaimsIdentity(claims)); } + /// + /// Construct an + /// + /// The value of + /// The for the public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) { DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); @@ -84,6 +109,7 @@ namespace Tgstation.Server.Host.Controllers Instance = AuthenticationContext?.InstanceUser?.Instance; } + /// public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { //validate the headers diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 58dec0d80d..02947c7272 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -4,15 +4,18 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { + /// + /// Main for the + /// [Route("/")] public sealed class HomeController : ApiController { @@ -25,21 +28,38 @@ namespace Tgstation.Server.Host.Controllers /// readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the + /// The for the /// - readonly IPasswordHasher passwordHasher; + readonly ICryptographySuite cryptographySuite; - public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, IPasswordHasher passwordHasher) : base(databaseContext, authenticationContextFactory) + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + /// The value of + /// The value of + public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite) : base(databaseContext, authenticationContextFactory) { this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); - this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); } + /// + /// Returns the version of the + /// + /// [Authorize] [HttpGet] - public JsonResult Home() => Json(Assembly.GetExecutingAssembly().GetName().Version); + public JsonResult Home() => Json(Application.Version); + /// + /// Attempt to authenticate a using + /// + /// The for the operation + /// A resulting in the of the operation [HttpPost] public async Task CreateToken(CancellationToken cancellationToken) { @@ -56,17 +76,15 @@ namespace Tgstation.Server.Host.Controllers if (user == null) return Unauthorized(); - if(user.PasswordHash != null) + if (user.PasswordHash != null) { - var hashResult = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, ApiHeaders.Password); - switch (hashResult) + var originalHash = user.PasswordHash; + if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password)) + return Unauthorized(); + if (user.PasswordHash != originalHash) { - case PasswordVerificationResult.Failed: - return Unauthorized(); - case PasswordVerificationResult.SuccessRehashNeeded: - user.PasswordHash = passwordHasher.HashPassword(user, ApiHeaders.Password); - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - break; + DatabaseContext.Users.Attach(user); + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); } } else diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index c97e498c3c..59d2b5c19a 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -8,24 +8,64 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { - public abstract class ModelController : ApiController + /// + /// An representing a + /// + /// The model being represented + public abstract class ModelController : ApiController where TModel : class { + /// + /// The of the + /// protected static readonly ModelAttribute ModelAttribute = (ModelAttribute)typeof(TModel).GetCustomAttributes(typeof(ModelAttribute), true).First(); + /// + /// Construct a + /// + /// The for the + /// The for the public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory) { } + /// + /// Attempt to create a + /// + /// The being created + /// The for the operation + /// A resulting in the of the operation [HttpPut] public virtual Task Create([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// + /// Attempt to read a + /// + /// The for the operation + /// A resulting in the of the operation [HttpGet] public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// + /// Attempt to update a + /// + /// The being updated + /// The for the operation + /// A resulting in the of the operation [HttpPost] public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// + /// Attempt to delete a + /// + /// The being deleted + /// The for the operation + /// A resulting in the of the operation [HttpDelete] public virtual Task Delete([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// + /// Attempt to list entries of the + /// + /// The for the operation + /// A resulting in the of the operation [HttpGet("/List")] public virtual Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 53a3e1639b..2cedce3c5e 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -2,22 +2,15 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using System; -using System.Collections.Generic; using System.Globalization; using System.IdentityModel.Tokens.Jwt; -using System.Linq; using System.Reflection; -using System.Security.Claims; -using System.Text; -using Tgstation.Server.Api; -using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Controllers; using Tgstation.Server.Host.Models; @@ -30,6 +23,11 @@ namespace Tgstation.Server.Host.Core /// sealed class Application { + /// + /// The version of the + /// + public static readonly Version Version = Assembly.GetExecutingAssembly().GetName().Version; + /// /// The for the /// @@ -64,17 +62,9 @@ namespace Tgstation.Server.Host.Core var workingDir = Environment.CurrentDirectory; var databaseConfigurationSection = configuration.GetSection(DatabaseConfiguration.Section); services.Configure(databaseConfigurationSection); - var generalConfigSection = configuration.GetSection(GeneralConfiguration.Section); - services.Configure(generalConfigSection); - - services.AddMvc(); - services.AddOptions(); - - var signingKey = generalConfigSection.Get().TokenSigningKey; - - if (signingKey == "default") - throw new InvalidOperationException("Do not use the default signing key!"); + services.AddOptions(); + const string scheme = "JwtBearer"; services.AddAuthentication((options) => { @@ -85,7 +75,7 @@ namespace Tgstation.Server.Host.Core jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)), + IssuerSigningKey = new SymmetricSecurityKey(TokenFactory.TokenSigningKey), ValidateIssuer = true, ValidIssuer = TokenFactory.TokenIssuer, @@ -105,7 +95,9 @@ namespace Tgstation.Server.Host.Core OnTokenValidated = ApiController.OnTokenValidated }; }); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs + + services.AddMvc(); var databaseConfiguration = databaseConfigurationSection.Get(); void ConfigureDatabase(DbContextOptionsBuilder builder) diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 7994ea69b6..7c7e3465c7 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using System; using System.Linq; using System.Threading; @@ -18,6 +17,7 @@ namespace Tgstation.Server.Host.Security /// The for the /// readonly ISystemIdentityFactory systemIdentityFactory; + /// /// The for the /// diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs index 7fa35d77ee..a3c5553030 100644 --- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Identity; using System; using System.Security.Cryptography; -using System.Text; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -10,31 +9,16 @@ namespace Tgstation.Server.Host.Security sealed class CryptographySuite : ICryptographySuite { /// - /// The length of secure strings used in the application + /// Generates a secure set of s /// - public const int SecureStringLength = 40; - - /// - /// Generates a secure ascii of length - /// - /// A secure ascii of length - static string GenerateSecureString() + /// A secure set of s + public static byte[] GetSecureBytes(int amount) { using (var rng = new RNGCryptoServiceProvider()) { - var byt = new byte[1]; - var result = new StringBuilder - { - Capacity = SecureStringLength - }; - while (result.Length < SecureStringLength) - { - rng.GetBytes(byt); - var chr = (char)byt[0]; - if (Char.IsLetterOrDigit(chr)) - result.Append(chr); - } - return result.ToString(); + var byt = new byte[amount]; + rng.GetBytes(byt); + return byt; } } @@ -58,5 +42,19 @@ namespace Tgstation.Server.Host.Security throw new ArgumentNullException(nameof(newPassword)); user.PasswordHash = passwordHasher.HashPassword(user, newPassword); } + + /// + public bool CheckUserPassword(User user, string password) + { + switch(passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password)) + { + case PasswordVerificationResult.Failed: + return false; + case PasswordVerificationResult.SuccessRehashNeeded: + user.PasswordHash = passwordHasher.HashPassword(user, password); + break; + } + return true; + } } } diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs index a5af46a902..2b1ee41a25 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs @@ -19,6 +19,11 @@ namespace Tgstation.Server.Host.Security /// InstanceUser InstanceUser { get; } + /// + /// Get the value of a given + /// + /// The of the right to get + /// The value of . Note that if is all based rights will return 0 int GetRight(RightsType rightsType); /// diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs index 4051035c0c..e0f3a225f2 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs @@ -8,8 +8,18 @@ namespace Tgstation.Server.Host.Security /// public interface IAuthenticationContextFactory { + /// + /// The the created + /// IAuthenticationContext CurrentAuthenticationContext { get; } + /// + /// Create an to populate + /// + /// The of the + /// The of the operation + /// The for the operation + /// A representing the running operation Task CreateAuthenticationContext(long userId, long? instanceId, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs index 21b3de1a7a..b19f805871 100644 --- a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Security /// /// Contains various cryptographic functions /// - interface ICryptographySuite + public interface ICryptographySuite { /// /// Sets a for a given @@ -13,5 +13,13 @@ namespace Tgstation.Server.Host.Security /// The whos is to be set /// The new password for the void SetUserPassword(User user, string newPassword); + + /// + /// Checks a given matches a given 's . This may result in being modified and this should be persisted + /// + /// The to check + /// The password to check + /// if matches the hash, otherwise + bool CheckUserPassword(User user, string password); } } diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 174f5858ee..e0e4c9af7b 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens; using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; @@ -7,7 +6,6 @@ using System.Reflection; using System.Security.Claims; using System.Text; using Tgstation.Server.Api.Models; -using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Security { @@ -16,17 +14,7 @@ namespace Tgstation.Server.Host.Security { public static readonly string TokenAudience = typeof(Token).Assembly.GetName().Name; public static readonly string TokenIssuer = Assembly.GetExecutingAssembly().GetName().Name; - - /// - /// The for the - /// - readonly GeneralConfiguration generalConfiguration; - - /// - /// Construct a - /// - /// The containing the value of - public TokenFactory(IOptions generalConfigurationOptions) => generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); + public static readonly byte[] TokenSigningKey = CryptographySuite.GetSecureBytes(256); /// public Token CreateToken(Models.User user) @@ -43,7 +31,7 @@ namespace Tgstation.Server.Host.Security new Claim(JwtRegisteredClaimNames.Aud, TokenAudience) }; - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(generalConfiguration.TokenSigningKey)); + var key = new SymmetricSecurityKey(TokenSigningKey); var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims)); return new Token { Bearer = new JwtSecurityTokenHandler().WriteToken(token) }; diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index a2125b38fb..2370aabf52 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -19,15 +19,11 @@ - - - PreserveNewest - PreserveNewest diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index c2960461f8..bfcbeffa32 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -17,9 +17,6 @@ } } }, - "General": { - "TokenSigningKey": "Default" - }, "Database": { "DatabaseType": "Sqlite", "ConnectionString": "Fake" From 35c72527e2e5ab8ad666d27e7b89dcfdfbf38beb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Apr 2018 15:44:05 -0400 Subject: [PATCH 088/397] Fix password hashings not being reapplied --- src/Tgstation.Server.Host/Controllers/HomeController.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 02947c7272..b7f01326dc 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -83,7 +83,13 @@ namespace Tgstation.Server.Host.Controllers return Unauthorized(); if (user.PasswordHash != originalHash) { - DatabaseContext.Users.Attach(user); + var updatedUser = new User + { + Id = user.Id, + PasswordHash = originalHash + }; + DatabaseContext.Users.Attach(updatedUser); + updatedUser.PasswordHash = user.PasswordHash; await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); } } From 933aa96edec94e661f376b1f3861594b38da3ba7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Apr 2018 15:44:45 -0400 Subject: [PATCH 089/397] Remove unused using, fix release build again --- src/Tgstation.Server.Host/Controllers/HomeController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index b7f01326dc..5260d1445b 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System; @@ -28,7 +27,7 @@ namespace Tgstation.Server.Host.Controllers /// readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the + /// The for the /// readonly ICryptographySuite cryptographySuite; From cf968c40694d7fa3ce6bf85c955d6aa186a5b80e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Apr 2018 15:54:02 -0400 Subject: [PATCH 090/397] Fix role distribution --- .../Controllers/ApiController.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 2df14a9d56..a75f026057 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -85,12 +85,15 @@ namespace Tgstation.Server.Host.Controllers var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; var enumerator = Enum.GetValues(typeof(RightsType)); - var claims = new List - { - Capacity = enumerator.Length - }; + var claims = new List(); foreach (RightsType I in enumerator) - claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, authenticationContext.GetRight(I)))); + { + var rightInt = authenticationContext.GetRight(I); + var right = (Enum)(ValueType)authenticationContext.GetRight(I); + foreach(Enum J in Enum.GetValues(RightsHelper.RightToType(I))) + if(right.HasFlag(J)) + claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, rightInt))); + } context.Principal.AddIdentity(new ClaimsIdentity(claims)); } From 410d241a6fb9cae7fa0af428e6eb15bdfdeca26f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 16 Apr 2018 18:21:47 -0400 Subject: [PATCH 091/397] Downgrade docker for now --- .dockerignore | 2 ++ build/Dockerfile | 4 ++-- src/Tgstation.Server.Host/Controllers/ApiController.cs | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.dockerignore b/.dockerignore index c3189160e5..341bd88cf1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,5 +5,7 @@ docker-compose.yml .vs .vscode legacy +packages +build */bin */obj diff --git a/build/Dockerfile b/build/Dockerfile index b0f90f3cad..2b1cb8c47d 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/dotnet:2.1-sdk AS build +FROM microsoft/dotnet:2.0-sdk AS build WORKDIR /src COPY tgstation-server.sln ./ @@ -36,7 +36,7 @@ RUN dotnet test -c Release FROM build AS publish RUN dotnet publish -c Release -o /app -FROM microsoft/dotnet:2.1-runtime +FROM microsoft/dotnet:2.0-runtime EXPOSE 80 WORKDIR /app diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index a75f026057..9c68b32ee3 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IdentityModel.Tokens.Jwt; -using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Tgstation.Server.Api; From a051ee4fe1ea6ec0adb7eea3f2a5937a6292919f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 16 Apr 2018 18:41:32 -0400 Subject: [PATCH 092/397] Fix claiming --- src/Tgstation.Server.Api/ApiHeaders.cs | 4 ++-- src/Tgstation.Server.Api/Rights/RightsHelper.cs | 2 +- src/Tgstation.Server.Host/Controllers/ApiController.cs | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 0235c5d401..5db3493ae5 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -33,12 +33,12 @@ namespace Tgstation.Server.Api /// /// The JWT authentication header scheme /// - const string jwtAuthenticationScheme = "bearer"; + const string jwtAuthenticationScheme = "Bearer"; /// /// The password authentication header scheme /// - const string passwordAuthenticationScheme = "password"; + const string passwordAuthenticationScheme = "Password"; /// /// The current diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index 2b3e0ddbc0..8db36400f4 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -46,7 +46,7 @@ namespace Tgstation.Server.Api.Rights /// The /// The right value /// A representing the claim role name - public static string RoleName(RightsType rightsType, int right) + public static string RoleName(RightsType rightsType, Enum right) { var enumType = typeMap[rightsType]; return String.Concat(enumType.Name, '.', Enum.GetName(enumType, right)); diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 9c68b32ee3..4c6bcd541f 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -88,10 +88,11 @@ namespace Tgstation.Server.Host.Controllers foreach (RightsType I in enumerator) { var rightInt = authenticationContext.GetRight(I); - var right = (Enum)(ValueType)authenticationContext.GetRight(I); - foreach(Enum J in Enum.GetValues(RightsHelper.RightToType(I))) + var rightEnum = RightsHelper.RightToType(I); + var right = (Enum)Enum.ToObject(rightEnum, authenticationContext.GetRight(I)); + foreach(Enum J in Enum.GetValues(rightEnum)) if(right.HasFlag(J)) - claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, rightInt))); + claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J))); } context.Principal.AddIdentity(new ClaimsIdentity(claims)); From e258f06c418bd2ce7c75887324a6281b6c9293c3 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 16 Apr 2018 19:43:04 -0400 Subject: [PATCH 093/397] Keeps the dockerfile xplat --- build/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 2b1cb8c47d..55d940a208 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -43,7 +43,8 @@ WORKDIR /app COPY --from=publish /app . -RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json +RUN mkdir /config_data +RUN mv appsettings.Docker.json /config_data/appsettings.Production.json VOLUME ["/config_data"] ENTRYPOINT ["dotnet", "Tgstation.Server.Host.Console.dll"] From cdd318524a3c54b5cffcc180d13e33f647aa98af Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 16 Apr 2018 19:58:32 -0400 Subject: [PATCH 094/397] Added docker only config. Give up windows docker support for now --- build/Dockerfile | 3 +-- tgstation-server.sln | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 55d940a208..2b1cb8c47d 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -43,8 +43,7 @@ WORKDIR /app COPY --from=publish /app . -RUN mkdir /config_data -RUN mv appsettings.Docker.json /config_data/appsettings.Production.json +RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json VOLUME ["/config_data"] ENTRYPOINT ["dotnet", "Tgstation.Server.Host.Console.dll"] diff --git a/tgstation-server.sln b/tgstation-server.sln index 58f4e6fea3..91b6751581 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -60,79 +60,115 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Docker|Any CPU = Docker|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Docker|Any CPU.Build.0 = Release|Any CPU {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Release|Any CPU.ActiveCfg = Release|Any CPU {A09C947F-4B9B-4AEE-AB50-47055EA05F18}.Release|Any CPU.Build.0 = Release|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Docker|Any CPU.Build.0 = Release|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {D36B99C4-E771-42D6-A95F-1102B3E236DF}.Release|Any CPU.Build.0 = Release|Any CPU {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Docker|Any CPU.Build.0 = Release|Any CPU {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A}.Release|Any CPU.Build.0 = Release|Any CPU {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Docker|Any CPU.Build.0 = Release|Any CPU {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B4A208D-A48A-4A5D-8B94-E2661138865D}.Release|Any CPU.Build.0 = Release|Any CPU {29927416-3B78-49A7-A560-5CCAA638B6B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {29927416-3B78-49A7-A560-5CCAA638B6B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29927416-3B78-49A7-A560-5CCAA638B6B4}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {29927416-3B78-49A7-A560-5CCAA638B6B4}.Docker|Any CPU.Build.0 = Release|Any CPU {29927416-3B78-49A7-A560-5CCAA638B6B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {29927416-3B78-49A7-A560-5CCAA638B6B4}.Release|Any CPU.Build.0 = Release|Any CPU {657C3624-3534-4A0C-B4E7-196FC830F547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {657C3624-3534-4A0C-B4E7-196FC830F547}.Debug|Any CPU.Build.0 = Debug|Any CPU + {657C3624-3534-4A0C-B4E7-196FC830F547}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {657C3624-3534-4A0C-B4E7-196FC830F547}.Docker|Any CPU.Build.0 = Release|Any CPU {657C3624-3534-4A0C-B4E7-196FC830F547}.Release|Any CPU.ActiveCfg = Release|Any CPU {657C3624-3534-4A0C-B4E7-196FC830F547}.Release|Any CPU.Build.0 = Release|Any CPU {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Docker|Any CPU.Build.0 = Release|Any CPU {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Release|Any CPU.ActiveCfg = Release|Any CPU {D38BE569-04B7-4C64-BDB5-683767B26FEB}.Release|Any CPU.Build.0 = Release|Any CPU {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Docker|Any CPU.Build.0 = Release|Any CPU {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0AC911F-7675-4A91-9499-D8A2E2390AAD}.Release|Any CPU.Build.0 = Release|Any CPU {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Docker|Any CPU.Build.0 = Release|Any CPU {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {93CA946D-50DE-4AD4-BADA-D0353F0AFFCC}.Release|Any CPU.Build.0 = Release|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Docker|Any CPU.Build.0 = Release|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE0E8F49-334F-49D7-A04D-D82E9AB7AA36}.Release|Any CPU.Build.0 = Release|Any CPU {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Docker|Any CPU.Build.0 = Release|Any CPU {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5301AF1-4F74-4982-BF24-95F23CC5D5B2}.Release|Any CPU.Build.0 = Release|Any CPU {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Docker|Any CPU.Build.0 = Release|Any CPU {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Release|Any CPU.ActiveCfg = Release|Any CPU {90D400B4-FCB0-447E-8FB2-1CF58EE7E55D}.Release|Any CPU.Build.0 = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Docker|Any CPU.Build.0 = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.Build.0 = Release|Any CPU {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Docker|Any CPU.Build.0 = Release|Any CPU {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Release|Any CPU.Build.0 = Release|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Docker|Any CPU.Build.0 = Release|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Release|Any CPU.Build.0 = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7500F776-4384-4B5F-A8D8-22461CAD108B}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {7500F776-4384-4B5F-A8D8-22461CAD108B}.Docker|Any CPU.Build.0 = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.Build.0 = Release|Any CPU {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Docker|Any CPU.Build.0 = Release|Any CPU {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Release|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.ActiveCfg = Release|Any CPU + {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection From 7f981d8c5ee9f0319ee8a415e6dfb3e3162be22e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 16 Apr 2018 21:21:43 -0400 Subject: [PATCH 095/397] TgsAuthorizeAttribute --- .../Models/Internal/ChatSettings.cs | 2 +- .../Rights/RightsHelper.cs | 2 +- src/Tgstation.Server.Api/Rights/RightsType.cs | 2 +- .../Controllers/HomeController.cs | 5 +- .../TgsAuthorizeAttribute.cs | 72 +++++++++++++++++++ 5 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 src/Tgstation.Server.Host/TgsAuthorizeAttribute.cs diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs index b98d62b906..a672895d3c 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Manage the server chat bots /// - [Model(RightsType.Chat, RequiresInstance = true)] + [Model(RightsType.ChatSettings, RequiresInstance = true)] public class ChatSettings { /// diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index 8db36400f4..ab4e16de4e 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Api.Rights { RightsType.Byond, typeof(ByondRights) }, { RightsType.DreamMaker, typeof(DreamMakerRights) }, { RightsType.DreamDaemon, typeof(DreamDaemonRights) }, - { RightsType.Chat, typeof(ChatSettingsRights) }, + { RightsType.ChatSettings, typeof(ChatSettingsRights) }, { RightsType.Configuration, typeof(ConfigurationRights) }, { RightsType.InstanceUser, typeof(InstanceUserRights) } }; diff --git a/src/Tgstation.Server.Api/Rights/RightsType.cs b/src/Tgstation.Server.Api/Rights/RightsType.cs index 5b4ff0722c..7ac7491661 100644 --- a/src/Tgstation.Server.Api/Rights/RightsType.cs +++ b/src/Tgstation.Server.Api/Rights/RightsType.cs @@ -32,7 +32,7 @@ /// /// /// - Chat, + ChatSettings, /// /// /// diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 5260d1445b..d839f45017 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System; using System.Linq; @@ -50,7 +49,7 @@ namespace Tgstation.Server.Host.Controllers /// Returns the version of the /// /// - [Authorize] + [TgsAuthorize] [HttpGet] public JsonResult Home() => Json(Application.Version); diff --git a/src/Tgstation.Server.Host/TgsAuthorizeAttribute.cs b/src/Tgstation.Server.Host/TgsAuthorizeAttribute.cs new file mode 100644 index 0000000000..2281587fae --- /dev/null +++ b/src/Tgstation.Server.Host/TgsAuthorizeAttribute.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.AspNetCore.Authorization; +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Host +{ + /// + /// Helper for using the with the system + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + sealed class TgsAuthorizeAttribute : AuthorizeAttribute + { + /// + /// Construct a + /// + public TgsAuthorizeAttribute() { } + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(AdministrationRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(RepositoryRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(ByondRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(DreamMakerRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(ChatSettingsRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(ConfigurationRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + + /// + /// Construct a for + /// + /// The rights required + public TgsAuthorizeAttribute(InstanceUserRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + } +} From 7bf342844d4b3d462b054495ea109ad34951bbcd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 17 Apr 2018 10:51:52 -0400 Subject: [PATCH 096/397] Repository interface --- .../Models/Internal/RepositorySettings.cs | 6 ++ src/Tgstation.Server.Api/Models/Repository.cs | 6 ++ .../Models/TestMergeParameters.cs | 5 + .../Components/IRepository.cs | 98 +++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/IRepository.cs diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index 11917d2f81..35a560f7da 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -41,6 +41,12 @@ namespace Tgstation.Server.Api.Models.Internal [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] public bool PushTestMergeCommits { get; set; } + /// + /// If test merge commits are signed with the username of the person who merged it. Note this only affects future commits + /// + [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] + public bool ShowTestMergeCommitters { get; set; } + /// /// How often the automatically updates in minutes /// diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 96ca42c58c..db3ed5b303 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -26,6 +26,12 @@ namespace Tgstation.Server.Api.Models [Permissions(DenyWrite = true)] public RevisionInformation RevisionInformation { get; set; } + /// + /// If the repository was cloned from GitHub.com. If this enables test merge functionality + /// + [Permissions(DenyWrite = true)] + public bool IsGitHub { get; set; } + /// /// The branch or tag HEAD points to /// diff --git a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs index 2c3ba33f02..ed27e6bb08 100644 --- a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs +++ b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs @@ -17,5 +17,10 @@ namespace Tgstation.Server.Api.Models /// [Required] public string PullRequestRevision { get; set; } + + /// + /// Optional comment about the test + /// + public string Comment { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/IRepository.cs new file mode 100644 index 0000000000..90dce08440 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IRepository.cs @@ -0,0 +1,98 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents an on-disk git repository + /// + interface IRepository + { + /// + /// Check if the is in a working state + /// + /// The for the operation + /// A resulting in if the is in a working state, otherwise + Task Exists(CancellationToken cancellationToken); + + /// + /// Check if the was cloned from GitHub.com + /// + /// The for the operation + /// A resulting in if the was cloned from GitHub.com, otherwise + Task IsGitHubRepository(CancellationToken cancellationToken); + + /// + /// Get the SHA of the HEAD + /// + /// The for the operation + /// A resulting in the SHA of the HEAD + Task GetHead(CancellationToken cancellationToken); + + /// + /// Get the current reference the HEAD is using. This can be a branch or tag + /// + /// The for the operation + /// A resulting in the current reference the HEAD is using. Will be if not on a branch or tag + Task GetReference(CancellationToken cancellationToken); + + /// + /// Get the current origin remote the is using + /// + /// The for the operation + /// A resulting in the current origin remote the is using + Task GetOrigin(CancellationToken cancellationToken); + + /// + /// Deletes the and clones a using an if provided + /// + /// The new remote url + /// The access string to clone the repository + /// The for the operation + /// A representing the running operation + Task SetOrigin(string newOrigin, string accessString, CancellationToken cancellationToken); + + /// + /// Checks out a given + /// + /// The sha or reference to checkout + /// The for the operation + /// A representing the running operation + Task CheckoutObject(string committish, CancellationToken cancellationToken); + + /// + /// Attempt to merge a GitHub pull request into HEAD + /// + /// The pull request number on the remote repository + /// The commit in the pull request to merge + /// The name of the merge committer + /// The e-mail of the merge committer + /// The body of the commit message + /// The access string to fetch from the origin repository + /// The for the operation + /// A resulting in the SHA of the new HEAD on success, on merge conflict + Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitBody, string accessToken, CancellationToken cancellationToken); + + /// + /// Fetch commits from the origin repository + /// + /// The access string to fetch from the origin repository + /// The for the operation + /// A representing the running operation + Task FetchOrigin(string accessString, CancellationToken cancellationToken); + + /// + /// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository + /// + /// The for the operation + /// A resulting in the SHA of the new HEAD + Task ResetToOrigin(CancellationToken cancellationToken); + + /// + /// Push the current repository HEAD to a temporary GitHub branch and then delete it + /// + /// The for the operation + /// A representing the running operation + Task PushHeadToTemporaryBranch(CancellationToken cancellationToken); + } +} From 982bed0674b79a83d37b2550077bb36224f6afee Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 17 Apr 2018 12:16:24 -0400 Subject: [PATCH 097/397] Implement repository --- .../Components/IRepository.cs | 52 ++--- .../Components/Repository.cs | 204 ++++++++++++++++++ .../Tgstation.Server.Host.csproj | 1 + 3 files changed, 220 insertions(+), 37 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Repository.cs diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/IRepository.cs index 90dce08440..a1c1f2c663 100644 --- a/src/Tgstation.Server.Host/Components/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/IRepository.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components @@ -6,51 +7,27 @@ namespace Tgstation.Server.Host.Components /// /// Represents an on-disk git repository /// - interface IRepository + interface IRepository : IDisposable { /// - /// Check if the is in a working state + /// If the was cloned from GitHub.com /// - /// The for the operation - /// A resulting in if the is in a working state, otherwise - Task Exists(CancellationToken cancellationToken); + bool IsGitHubRepository { get; } /// - /// Check if the was cloned from GitHub.com + /// The SHA of the HEAD /// - /// The for the operation - /// A resulting in if the was cloned from GitHub.com, otherwise - Task IsGitHubRepository(CancellationToken cancellationToken); + string Head { get; } /// - /// Get the SHA of the HEAD + /// The current reference the HEAD is using. This can be a branch or tag /// - /// The for the operation - /// A resulting in the SHA of the HEAD - Task GetHead(CancellationToken cancellationToken); + string Reference { get; } /// - /// Get the current reference the HEAD is using. This can be a branch or tag + /// The current origin remote the is using /// - /// The for the operation - /// A resulting in the current reference the HEAD is using. Will be if not on a branch or tag - Task GetReference(CancellationToken cancellationToken); - - /// - /// Get the current origin remote the is using - /// - /// The for the operation - /// A resulting in the current origin remote the is using - Task GetOrigin(CancellationToken cancellationToken); - - /// - /// Deletes the and clones a using an if provided - /// - /// The new remote url - /// The access string to clone the repository - /// The for the operation - /// A representing the running operation - Task SetOrigin(string newOrigin, string accessString, CancellationToken cancellationToken); + string Origin { get; } /// /// Checks out a given @@ -71,7 +48,7 @@ namespace Tgstation.Server.Host.Components /// The access string to fetch from the origin repository /// The for the operation /// A resulting in the SHA of the new HEAD on success, on merge conflict - Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitBody, string accessToken, CancellationToken cancellationToken); + Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitBody, string accessString, CancellationToken cancellationToken); /// /// Fetch commits from the origin repository @@ -89,10 +66,11 @@ namespace Tgstation.Server.Host.Components Task ResetToOrigin(CancellationToken cancellationToken); /// - /// Push the current repository HEAD to a temporary GitHub branch and then delete it + /// Force push the current repository HEAD to ; /// + /// The access string to fetch from the origin repository /// The for the operation /// A representing the running operation - Task PushHeadToTemporaryBranch(CancellationToken cancellationToken); + Task PushHeadToTemporaryBranch(string accessString, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Repository.cs b/src/Tgstation.Server.Host/Components/Repository.cs new file mode 100644 index 0000000000..aa031d6fee --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Repository.cs @@ -0,0 +1,204 @@ +using LibGit2Sharp; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class Repository : IRepository + { + /// + /// The branch name used for publishing testmerge commits + /// + public const string RemoteTemporaryBranchName = "___TGSTempBranch"; + + /// + public bool IsGitHubRepository { get; } + + /// + public string Head => repository.Head.Tip.Sha; + + /// + public string Reference => repository.Head.FriendlyName; + + /// + public string Origin => repository.Network.Remotes.First().Url; + + /// + /// The for the + /// + readonly LibGit2Sharp.IRepository repository; + + /// + /// Construct a + /// + /// The value of + public Repository(LibGit2Sharp.IRepository repository) + { + this.repository = repository ?? throw new ArgumentNullException(nameof(repository)); + IsGitHubRepository = Origin.ToUpperInvariant().Contains("GITHUB.COM"); + } + + /// + public void Dispose() => repository.Dispose(); + + /// + /// Convert to an "https://@{url} equivalent + /// + /// The containing authentication info for the remote repository + /// An authenticated URL for accessing the remote repository + string GenerateAuthUrl(string accessString) + { + if (String.IsNullOrWhiteSpace(accessString)) + return Origin; + const string HttProtocolSecure = "HTTPS://"; + if (!Origin.ToUpperInvariant().StartsWith(HttProtocolSecure, StringComparison.InvariantCulture)) + throw new InvalidOperationException("Cannot use access string without HTTPS remote!"); + //ONLY support https urls + return Origin.ToUpperInvariant().Replace(HttProtocolSecure, String.Concat(HttProtocolSecure, accessString, '@')); + } + + /// + /// Runs a blocking force checkout to + /// + /// The committish to checkout + void RawCheckout(string committish) + { + Commands.Checkout(repository, committish, new CheckoutOptions + { + CheckoutModifiers = CheckoutModifiers.Force + }); + repository.RemoveUntrackedFiles(); + } + + /// + public Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitBody, string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + var Refspec = new List(); + var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", pullRequestNumber); + var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", pullRequestNumber, prBranchName); + Refspec.Add(String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", pullRequestNumber, prBranchName)); + var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", pullRequestNumber); + + var remote = repository.Network.Remotes.Add("temp_pr_fetch", GenerateAuthUrl(accessString)); + try + { + cancellationToken.ThrowIfCancellationRequested(); + Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions + { + Prune = true, + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested + }, logMessage); + } + catch (UserCancelledException) { } + finally + { + repository.Network.Remotes.Remove(remote.Name); + //commit is there and we never gc so + repository.Branches.Remove(localBranchName); + repository.Branches.Remove(prBranchName); + } + + cancellationToken.ThrowIfCancellationRequested(); + + var originalCommit = repository.Head; + + var result = repository.Merge(targetCommit, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions + { + CommitOnSuccess = true, + FailOnConflict = true, + FastForwardStrategy = FastForwardStrategy.NoFastForward, + SkipReuc = true, + }); + + if (result.Status != MergeStatus.NonFastForward) + { + RawCheckout(originalCommit.FriendlyName ?? originalCommit.Tip.Sha); + return null; + } + + return result.Commit.Sha; + + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task CheckoutObject(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() => RawCheckout(committish), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task FetchOrigin(string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + var remote = repository.Network.Remotes.First(); + try + { + Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions + { + Prune = true, + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested + }, "Fetch origin commits"); + } + catch (UserCancelledException) + { + cancellationToken.ThrowIfCancellationRequested(); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task PushHeadToTemporaryBranch(string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + var branch = repository.CreateBranch(RemoteTemporaryBranchName); + try + { + cancellationToken.ThrowIfCancellationRequested(); + var remote = repository.Network.Remotes.Add("temp_push", GenerateAuthUrl(accessString)); + try + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + repository.Network.Push(remote, String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName), new PushOptions + { + OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested, + OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested, + OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested + }); + } + catch (UserCancelledException) + { + cancellationToken.ThrowIfCancellationRequested(); + } + } + finally + { + repository.Network.Remotes.Remove(remote.Name); + } + } + finally + { + repository.Branches.Remove(branch); + } + + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task ResetToOrigin(CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + if (!repository.Head.IsTracking) + throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!"); + var trackedBranch = repository.Head.TrackedBranch; + Commands.Checkout((LibGit2Sharp.Repository)repository, repository.Head.TrackedBranch, new CheckoutOptions + { + CheckoutModifiers = CheckoutModifiers.Force + }); + repository.RemoveUntrackedFiles(); + return trackedBranch.Tip.Sha; + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } +} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 2370aabf52..2dfa73f107 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -35,6 +35,7 @@ + From f6b132990b5f80f15464338b08406fa74c50f068 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Apr 2018 11:22:04 -0400 Subject: [PATCH 098/397] IOMananger and repository manager --- .../Components/IRepository.cs | 8 + .../Components/IRepositoryManager.cs | 27 +++ .../Components/Repository.cs | 35 ++- .../Components/RepositoryManager.cs | 56 +++++ .../Core/DefaultIOManager.cs | 207 ++++++++++++++++++ src/Tgstation.Server.Host/Core/IIOManager.cs | 133 +++++++++++ .../Core/ResolvingIOManager.cs | 39 ++++ 7 files changed, 497 insertions(+), 8 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IRepositoryManager.cs create mode 100644 src/Tgstation.Server.Host/Components/RepositoryManager.cs create mode 100644 src/Tgstation.Server.Host/Core/DefaultIOManager.cs create mode 100644 src/Tgstation.Server.Host/Core/IIOManager.cs create mode 100644 src/Tgstation.Server.Host/Core/ResolvingIOManager.cs diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/IRepository.cs index a1c1f2c663..e52261b3dc 100644 --- a/src/Tgstation.Server.Host/Components/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/IRepository.cs @@ -72,5 +72,13 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// A representing the running operation Task PushHeadToTemporaryBranch(string accessString, CancellationToken cancellationToken); + + /// + /// Copies the current working directory to a given + /// + /// The path to copy repository contents to + /// The for the operation + /// A representing the running operation + Task CopyTo(string path, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs new file mode 100644 index 0000000000..63800c9386 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -0,0 +1,27 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Factory for creating and loading s + /// + interface IRepositoryManager + { + /// + /// Attempt to load the from the default location + /// + /// The for the operation + /// The loaded + Task LoadRepository(CancellationToken cancellationToken); + + /// + /// Delete the current and replaces it with a clone of the repository at + /// + /// The location of the remote repository to clone + /// The access string to clone from + /// The for the operation + /// The newly cloned + Task CloneRepository(string url, string accessString, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Repository.cs b/src/Tgstation.Server.Host/Components/Repository.cs index aa031d6fee..994f71b249 100644 --- a/src/Tgstation.Server.Host/Components/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { @@ -33,13 +34,20 @@ namespace Tgstation.Server.Host.Components /// readonly LibGit2Sharp.IRepository repository; + /// + /// The for the + /// + readonly IIOManager ioMananger; + /// /// Construct a /// /// The value of - public Repository(LibGit2Sharp.IRepository repository) + /// The value of + public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger) { this.repository = repository ?? throw new ArgumentNullException(nameof(repository)); + this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); IsGitHubRepository = Origin.ToUpperInvariant().Contains("GITHUB.COM"); } @@ -47,19 +55,22 @@ namespace Tgstation.Server.Host.Components public void Dispose() => repository.Dispose(); /// - /// Convert to an "https://@{url} equivalent + /// Convert to an "https://@{url} equivalent /// + /// The URL to convert /// The containing authentication info for the remote repository /// An authenticated URL for accessing the remote repository - string GenerateAuthUrl(string accessString) + public static string GenerateAuthUrl(string url, string accessString) { + if (url == null) + throw new ArgumentNullException(nameof(url)); if (String.IsNullOrWhiteSpace(accessString)) - return Origin; + return url; const string HttProtocolSecure = "HTTPS://"; - if (!Origin.ToUpperInvariant().StartsWith(HttProtocolSecure, StringComparison.InvariantCulture)) + if (!url.ToUpperInvariant().StartsWith(HttProtocolSecure, StringComparison.InvariantCulture)) throw new InvalidOperationException("Cannot use access string without HTTPS remote!"); //ONLY support https urls - return Origin.ToUpperInvariant().Replace(HttProtocolSecure, String.Concat(HttProtocolSecure, accessString, '@')); + return url.ToUpperInvariant().Replace(HttProtocolSecure, String.Concat(HttProtocolSecure, accessString, '@')); } /// @@ -84,7 +95,7 @@ namespace Tgstation.Server.Host.Components Refspec.Add(String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", pullRequestNumber, prBranchName)); var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", pullRequestNumber); - var remote = repository.Network.Remotes.Add("temp_pr_fetch", GenerateAuthUrl(accessString)); + var remote = repository.Network.Remotes.Add("temp_pr_fetch", GenerateAuthUrl(Origin, accessString)); try { cancellationToken.ThrowIfCancellationRequested(); @@ -157,7 +168,7 @@ namespace Tgstation.Server.Host.Components try { cancellationToken.ThrowIfCancellationRequested(); - var remote = repository.Network.Remotes.Add("temp_push", GenerateAuthUrl(accessString)); + var remote = repository.Network.Remotes.Add("temp_push", GenerateAuthUrl(Origin, accessString)); try { cancellationToken.ThrowIfCancellationRequested(); @@ -200,5 +211,13 @@ namespace Tgstation.Server.Host.Components repository.RemoveUntrackedFiles(); return trackedBranch.Tip.Sha; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public async Task CopyTo(string path, CancellationToken cancellationToken) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + await ioMananger.CopyDirectory(".", path, new List { ".git" }, cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs new file mode 100644 index 0000000000..3080659d08 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -0,0 +1,56 @@ +using LibGit2Sharp; +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class RepositoryManager : IRepositoryManager + { + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// Construct a + /// + /// The value of + public RepositoryManager(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + + /// + public async Task CloneRepository(string url, string accessString, CancellationToken cancellationToken) + { + await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false); + + await Task.Factory.StartNew(() => + { + string path = null; + try + { + path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url, accessString), ioManager.ResolvePath("."), new CloneOptions + { + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + RecurseSubmodules = true, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, + RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested + }); + } + catch (UserCancelledException) { } + cancellationToken.ThrowIfCancellationRequested(); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + + return await LoadRepository(cancellationToken).ConfigureAwait(false); + } + + /// + public Task LoadRepository(CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + var repo = new LibGit2Sharp.Repository(ioManager.ResolvePath(".")); + return (IRepository)new Repository(repo, ioManager); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } +} diff --git a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs new file mode 100644 index 0000000000..f0644d6f33 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + /// that resolves paths to + /// + class DefaultIOManager : IIOManager + { + /// + /// Default buffer size used by .NET + /// + public const int DefaultBufferSize = 4096; + + /// + /// Recursively empty a directory + /// + /// of the directory to empty + /// The for the operation + /// A representing the running operation + static async Task NormalizeAndDelete(DirectoryInfo dir, CancellationToken cancellationToken) + { + var tasks = new List(); + + foreach (var subDir in dir.EnumerateDirectories()) + { + cancellationToken.ThrowIfCancellationRequested(); + tasks.Add(NormalizeAndDelete(subDir, cancellationToken)); + } + foreach (var file in dir.EnumerateFiles()) + { + cancellationToken.ThrowIfCancellationRequested(); + file.Attributes = FileAttributes.Normal; + file.Delete(); + } + await Task.WhenAll(tasks).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + dir.Delete(true); + } + + + /// + /// Copies a directory from to + /// + /// The source directory path + /// The destination directory path + /// Files and folders to ignore at the root level + /// The for the operation + /// A representing the running operation + async Task CopyDirectoryImpl(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken) + { + await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); + + var dir = new DirectoryInfo(src); + cancellationToken.ThrowIfCancellationRequested(); + + var dirs = dir.EnumerateDirectories(); + var files = dir.EnumerateFiles(); + + var fileCopyTasks = files.Select(x => + { + cancellationToken.ThrowIfCancellationRequested(); + if (ignore != null && ignore.Contains(x.Name)) + return Task.CompletedTask; + return CopyFile(x.FullName, Path.Combine(dest, x.Name), cancellationToken); + }); + + var directoryCopyTasks = dirs.Select(x => + { + cancellationToken.ThrowIfCancellationRequested(); + if (ignore != null && ignore.Contains(x.Name)) + return Task.CompletedTask; + return CopyDirectoryImpl(x.FullName, Path.Combine(dest, x.Name), null, cancellationToken); + }); + + await Task.WhenAll(fileCopyTasks.Concat(directoryCopyTasks)).ConfigureAwait(false); + } + + /// + public async Task CopyDirectory(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken) + { + if (dest == null) + throw new ArgumentNullException(nameof(src)); + if (dest == null) + throw new ArgumentNullException(nameof(src)); + + src = ResolvePath(src); + dest = ResolvePath(dest); + await CopyDirectoryImpl(src, dest, ignore, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task AppendAllText(string path, string additional_contents, CancellationToken cancellationToken) + { + if (additional_contents == null) + throw new ArgumentNullException(nameof(additional_contents)); + using (var destStream = new FileStream(ResolvePath(path), FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, DefaultBufferSize, true)) + { + var buf = Encoding.UTF8.GetBytes(additional_contents); + await destStream.WriteAsync(buf, 0, buf.Length, cancellationToken).ConfigureAwait(false); + } + } + + /// + public string ConcatPath(params string[] paths) + { + if (paths == null) + throw new ArgumentNullException(nameof(paths)); + return Path.Combine(paths); + } + + /// + public async Task CopyFile(string src, string dest, CancellationToken cancellationToken) + { + if (src == null) + throw new ArgumentNullException(nameof(src)); + if (dest == null) + throw new ArgumentNullException(nameof(dest)); + using (var srcStream = new FileStream(ResolvePath(src), FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, DefaultBufferSize, true)) + using (var destStream = new FileStream(ResolvePath(dest), FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, DefaultBufferSize, true)) + await srcStream.CopyToAsync(destStream, DefaultBufferSize, cancellationToken).ConfigureAwait(false); + } + + /// + public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.CreateDirectory(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public async Task DeleteDirectory(string path, CancellationToken cancellationToken) + { + path = ResolvePath(path); + var di = new DirectoryInfo(path); + if (!di.Exists) + return; + await NormalizeAndDelete(di, cancellationToken).ConfigureAwait(false); + } + + /// + public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Delete(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + public Task FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public string GetDirectoryName(string path) => Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path))); + + /// + public string GetFileName(string path) => Path.GetFileName(path ?? throw new ArgumentNullException(nameof(path))); + + /// + public string GetFileNameWithoutExtension(string path) => Path.GetFileNameWithoutExtension(path ?? throw new ArgumentNullException(nameof(path))); + + /// + public Task> GetFilesWithExtension(string path, string extension, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + path = ResolvePath(path); + if (extension == null) + throw new ArgumentNullException(extension); + var results = new List(); + foreach (var I in Directory.EnumerateFiles(path, String.Format(CultureInfo.InvariantCulture, "*.{0}", extension), SearchOption.TopDirectoryOnly)) + { + cancellationToken.ThrowIfCancellationRequested(); + results.Add(I); + } + return results; + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task MoveFile(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source))); + destination = ResolvePath(destination); + File.Move(source, destination); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public async Task ReadAllBytes(string path, CancellationToken cancellationToken) + { + path = ResolvePath(path); + using (var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, DefaultBufferSize, true)) + { + byte[] buf; + buf = new byte[file.Length]; + await file.ReadAsync(buf, 0, (int)file.Length, cancellationToken).ConfigureAwait(false); + return buf; + } + } + + /// + public virtual string ResolvePath(string path) => Path.GetFullPath(path ?? throw new ArgumentNullException(nameof(path))); + + /// + public async Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken) + { + path = ResolvePath(path); + using (var file = File.Open(path, FileMode.Create, FileAccess.Write)) + await file.WriteAsync(contents, 0, contents.Length, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/IIOManager.cs b/src/Tgstation.Server.Host/Core/IIOManager.cs new file mode 100644 index 0000000000..2083ad84aa --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IIOManager.cs @@ -0,0 +1,133 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Interface for using filesystems + /// + interface IIOManager + { + /// + /// Retrieve the full path of some given a relative path. Must be used before passing relative paths to other APIs. All other operations in this call this internally on given paths + /// + /// Some path to retrieve the full path of + /// as a full canonical path + string ResolvePath(string path); + + /// + /// Gets the file name portion of a + /// + /// The path to get the file name of + /// The file name portion of + string GetFileName(string path); + + /// + /// Gets the file name portion of a with + /// + /// The path to get the file name of + /// The file name portion of + string GetFileNameWithoutExtension(string path); + + /// + /// Copies a directory from to + /// + /// The source directory path + /// The destination directory path + /// Files and folders to ignore at the root level + /// The for the operation + /// A representing the running operation + Task CopyDirectory(string src, string dest, IEnumerable ignore, CancellationToken cancellationToken); + + /// + /// Check that the file at exists + /// + /// The file to check for existence + /// The for the operation + /// A resulting in if the file at exists, otherwise + Task FileExists(string path, CancellationToken cancellationToken); + + /// + /// Returns all the contents of a file at as a array + /// + /// The path of the file to read + /// A for the operation + /// A that results in the contents of a file at + Task ReadAllBytes(string path, CancellationToken cancellationToken); + + /// + /// Writes some to a file at overwriting previous content + /// + /// The path of the file to write + /// The contents of the file + /// A for the operation + /// A representing the running operation + Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken); + + /// + /// Copy a file from to + /// + /// The source file to copy + /// The destination path + /// A for the operation + /// A representing the running operation + Task CopyFile(string src, string dest, CancellationToken cancellationToken); + + /// + /// Gets the directory portion of a given + /// + /// A path to check + /// The directory portion of the given + string GetDirectoryName(string path); + + /// + /// Gets a list of files in with the given + /// + /// The directory which contains the files + /// The extension to look for without the preceeding "." + /// A for the operation + /// A resulting in a list of paths to files in with the given + Task> GetFilesWithExtension(string path, string extension, CancellationToken cancellationToken); + + /// + /// Deletes a file at + /// + /// The path of the file to delete + /// A for the operation + /// A representing the running operation + Task DeleteFile(string path, CancellationToken cancellationToken); + + /// + /// Create a directory at + /// + /// The path of the directory to create + /// A for the operation + /// A representing the running operation + Task CreateDirectory(string path, CancellationToken cancellationToken); + + /// + /// Recursively delete a directory + /// + /// The path to the directory to delete + /// A for the operation + /// A representing the running operation + Task DeleteDirectory(string path, CancellationToken cancellationToken); + + /// + /// Combines an array of strings into a path + /// + /// The paths to combine + /// The combined path + string ConcatPath(params string[] paths); + + /// + /// Moves a file at to + /// + /// The source file path + /// The destination path + /// A for the operation + /// A representing the running operation + Task MoveFile(string source, string destination, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Core/ResolvingIOManager.cs b/src/Tgstation.Server.Host/Core/ResolvingIOManager.cs new file mode 100644 index 0000000000..f7d4bf2045 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ResolvingIOManager.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; + +namespace Tgstation.Server.Host.Core +{ + /// + /// An that resolve relative paths from another to a subdirectory of that + /// + sealed class ResolvingIOManager : DefaultIOManager + { + /// + /// Path to the subdirectory attached to path resolutions + /// + readonly string subdirectory; + + /// + /// Construct a + /// + /// The that resolves to the directory to work out of + /// The value of + public ResolvingIOManager(IIOManager parent, string _subdirectory) + { + if(parent == null) + throw new ArgumentNullException(nameof(parent)); + if (_subdirectory == null) + throw new ArgumentNullException(nameof(_subdirectory)); + + subdirectory = ConcatPath(parent.ResolvePath("."), _subdirectory); + } + + /// + public override string ResolvePath(string path) + { + if (!Path.IsPathRooted(path)) + return base.ResolvePath(ConcatPath(subdirectory, path)); + return path; + } + } +} \ No newline at end of file From f296a407876755927244328635699bf2c88e8f38 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Apr 2018 15:15:54 -0400 Subject: [PATCH 099/397] JobManager --- src/Tgstation.Server.Host/Core/Application.cs | 22 ++- src/Tgstation.Server.Host/Core/IJobManager.cs | 33 ++++ src/Tgstation.Server.Host/Core/JobHandler.cs | 63 ++++++++ src/Tgstation.Server.Host/Core/JobManager.cs | 143 ++++++++++++++++++ .../Models/IDatabaseContext.cs | 7 +- .../Tgstation.Server.Host.csproj | 1 - src/Tgstation.Server.Host/appsettings.json | 4 +- 7 files changed, 257 insertions(+), 16 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/IJobManager.cs create mode 100644 src/Tgstation.Server.Host/Core/JobHandler.cs create mode 100644 src/Tgstation.Server.Host/Core/JobManager.cs diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 2cedce3c5e..87626da1fc 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,11 +1,11 @@ -using Cyberboss.AspNetCore.AsyncInitializer; -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using System; using System.Globalization; @@ -34,16 +34,16 @@ namespace Tgstation.Server.Host.Core readonly IConfiguration configuration; /// - /// The for the + /// The for the /// - readonly IHostingEnvironment hostingEnvironment; + readonly Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment; /// /// Construct an /// /// The value of /// The value of - public Application(IConfiguration configuration, IHostingEnvironment hostingEnvironment) + public Application(IConfiguration configuration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment) { this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); @@ -131,6 +131,10 @@ namespace Tgstation.Server.Host.Core services.AddSingleton, PasswordHasher>(); services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(x => x.GetRequiredService()); } /// @@ -144,13 +148,7 @@ namespace Tgstation.Server.Host.Core if (hostingEnvironment.IsDevelopment()) applicationBuilder.UseDeveloperExceptionPage(); - - applicationBuilder.UseAsyncInitialization(async (cancellationToken) => - { - using (var scope = applicationBuilder.ApplicationServices.CreateScope()) - await scope.ServiceProvider.GetRequiredService().Initialize(cancellationToken).ConfigureAwait(false); - }); - + applicationBuilder.UseAuthentication(); applicationBuilder.UseMvc(); } diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs new file mode 100644 index 0000000000..13d30ad436 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core +{ + interface IJobManager + { + /// + /// Registers a given and begins running it + /// + /// The + /// The operation to run + /// The for the operation + /// A representing a running operation + Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken); + + /// + /// Wait for a given to complete + /// + /// The to wait for + /// The for the operation + /// A representing a running operation + Task WaitForJob(Job job, CancellationToken cancellationToken); + + /// + /// Cancels a give + /// + /// The to cancel + void CancelJob(Job job); + } +} diff --git a/src/Tgstation.Server.Host/Core/JobHandler.cs b/src/Tgstation.Server.Host/Core/JobHandler.cs new file mode 100644 index 0000000000..ab05719268 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/JobHandler.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Class for pairing s with s + /// + sealed class JobHandler : IDisposable + { + /// + /// The being run + /// + readonly Task task; + /// + /// The for + /// + readonly CancellationTokenSource cancellationTokenSource; + + /// + /// Construct a + /// + /// The value of + /// The value of + JobHandler(Task task, CancellationTokenSource cancellationTokenSource) + { + this.task = task; + this.cancellationTokenSource = cancellationTokenSource; + } + + /// + public void Dispose() => cancellationTokenSource.Dispose(); + + /// + /// Wait for to complete + /// + /// The for the operation + /// A representing the running operation + public async Task Wait(CancellationToken cancellationToken) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + using (cancellationToken.Register(() => tcs.SetCanceled())) + await Task.WhenAny(tcs.Task, task).ConfigureAwait(false); + } + + /// + /// Cancels + /// + public void Cancel() => cancellationTokenSource.Cancel(); + + /// + /// Create a + /// + /// A taking a and returning a that the will wrap + /// A new + public static JobHandler Create(Func job) + { + var cts = new CancellationTokenSource(); + return new JobHandler(job(cts.Token), cts); + } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs new file mode 100644 index 0000000000..94fceefca0 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -0,0 +1,143 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class JobManager : IHostedService, IJobManager + { + /// + /// The for the + /// + readonly IServiceProvider serviceProvider; + /// + /// of to running s + /// + readonly Dictionary jobs; + + /// + /// Construct a + /// + /// The value of + public JobManager(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + jobs = new Dictionary(); + } + + /// + /// Gets the for a given if it exists + /// + /// The to get the for + /// The + JobHandler CheckGetJob(Job job) + { + lock (this) + { + if (!jobs.TryGetValue(job.Id, out JobHandler jobHandler)) + throw new InvalidOperationException("Job not running!"); + return jobHandler; + } + } + + /// + /// Runner for s + /// + /// The being run + /// The operation for the + /// The for the operation + /// A representing the running operation + async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) + { + bool cancelled; + try + { + await operation(cancellationToken).ConfigureAwait(false); + cancelled = false; + } + catch (OperationCanceledException) + { + cancelled = true; + } + + using (var scope = serviceProvider.CreateScope()) + { + var databaseContext = scope.ServiceProvider.GetRequiredService(); + job = new Job { Id = job.Id }; + databaseContext.Jobs.Attach(job); + if (cancelled) + job.Cancelled = true; + job.StoppedAt = DateTimeOffset.Now; + await databaseContext.Save(default).ConfigureAwait(false); + } + } + + /// + public async Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken) + { + using (var scope = serviceProvider.CreateScope()) + { + var databaseContext = scope.ServiceProvider.GetRequiredService(); + job.StartedAt = DateTimeOffset.Now; + databaseContext.Jobs.Add(job); + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + var jobHandler = JobHandler.Create(x => RunJob(job, operation, x)); + lock (this) + jobs.Add(job.Id, jobHandler); + } + } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + using (var scope = serviceProvider.CreateScope()) + { + var databaseContext = scope.ServiceProvider.GetRequiredService(); + await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); + + //mark all jobs as cancelled + var enumerator = await databaseContext.Jobs.Where(y => !y.Cancelled && y.StoppedAt == null).Select(y => y.Id).ToAsyncEnumerable().ToList(cancellationToken).ConfigureAwait(false); + foreach(var I in enumerator) + { + var job = new Job { Id = I }; + databaseContext.Jobs.Attach(job); + job.Cancelled = true; + } + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } + } + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + var joinTasks = jobs.Select(x => + { + x.Value.Cancel(); + return x.Value.Wait(cancellationToken); + }); + await Task.WhenAll(joinTasks).ConfigureAwait(false); + foreach (var job in jobs) + job.Value.Dispose(); + jobs.Clear(); + } + + /// + public async Task WaitForJob(Job job, CancellationToken cancellationToken) + { + var handler = CheckGetJob(job); + await handler.Wait(cancellationToken).ConfigureAwait(false); + lock (this) + jobs.Remove(job.Id); + handler.Dispose(); + } + + /// + public void CancelJob(Job job) => CheckGetJob(job).Cancel(); + } +} diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 9ed9acf3a4..c9076a354b 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -15,10 +15,15 @@ namespace Tgstation.Server.Host.Models DbSet Users { get; } /// - /// The s in the + /// The s in the /// DbSet Instances { get; } + /// + /// The s in the + /// + DbSet Jobs { get; } + /// /// Get the in the /// diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 2370aabf52..9ccee01f9b 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -34,7 +34,6 @@ - diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index bfcbeffa32..12aa0e9cf9 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -18,7 +18,7 @@ } }, "Database": { - "DatabaseType": "Sqlite", - "ConnectionString": "Fake" + "DatabaseType": "SqlServer", + "ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True" } } From 0092613f1d3d5727e9142dda59079e9fcdf2c174 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Apr 2018 16:37:19 -0400 Subject: [PATCH 100/397] Instance Manager --- .../Components/IInstance.cs | 8 ++ .../Components/IInstanceFactory.cs | 9 ++ .../Components/IInstanceManager.cs | 43 ++++++ .../Components/InstanceManager.cs | 132 ++++++++++++++++++ src/Tgstation.Server.Host/Core/Application.cs | 5 + 5 files changed, 197 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/IInstance.cs create mode 100644 src/Tgstation.Server.Host/Components/IInstanceFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/IInstanceManager.cs create mode 100644 src/Tgstation.Server.Host/Components/InstanceManager.cs diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs new file mode 100644 index 0000000000..bdd3f392e5 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.Hosting; + +namespace Tgstation.Server.Host.Components +{ + interface IInstance : IHostedService + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs new file mode 100644 index 0000000000..c76afabf4f --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -0,0 +1,9 @@ +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components +{ + interface IInstanceFactory + { + IInstance CreateInstance(Instance metadata); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs new file mode 100644 index 0000000000..d63384a3fd --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -0,0 +1,43 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// For managing s + /// + interface IInstanceManager + { + /// + /// Get the associated with given + /// + /// The of the desired + /// The associated with the given + IInstance GetInstance(Models.Instance metadata); + + /// + /// Online an + /// + /// The of the desired + /// The for the operation + /// A representing the running operation + Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken); + + /// + /// Offline an + /// + /// The of the desired + /// The for the operation + /// A representing the running operation + Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken); + + /// + /// Move an + /// + /// The of the desired + /// The new path of the . will have this set on if the operation completes successfully + /// The for the operation + /// A representing the running operation + Task MoveInstance(Models.Instance metadata, string newPath, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs new file mode 100644 index 0000000000..73477bbd76 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -0,0 +1,132 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class InstanceManager : IInstanceManager, IHostedService + { + /// + /// The for the + /// + readonly IInstanceFactory instanceFactory; + /// + /// The for the + /// + readonly IServiceProvider serviceProvider; + /// + /// The for the + /// + readonly IIOManager ioManager; + /// + /// Map of s to respective s + /// + readonly Dictionary instances; + + /// + /// Construct an + /// + /// The value of + /// The value of + /// The value of + public InstanceManager(IInstanceFactory instanceFactory, IServiceProvider serviceProvider, IIOManager ioManager) + { + this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); + this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + instances = new Dictionary(); + } + + /// + public IInstance GetInstance(Instance metadata) + { + lock (this) + { + if (!instances.TryGetValue(metadata.Id, out IInstance instance)) + throw new InvalidOperationException("Instance not online!"); + return instance; + } + } + + /// + public async Task MoveInstance(Instance instance, string newPath, CancellationToken cancellationToken) + { + if (newPath == null) + throw new ArgumentNullException(nameof(newPath)); + if (instance.Online) + await OfflineInstance(instance, cancellationToken).ConfigureAwait(false); + Task instanceOnlineTask = null; + try + { + var oldPath = instance.Path; + await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); + instance.Path = ioManager.ResolvePath(newPath); + instanceOnlineTask = OnlineInstance(instance, default); + await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); + } + finally + { + if (instance.Online) + if (instanceOnlineTask == null) + await OnlineInstance(instance, default).ConfigureAwait(false); + else + await instanceOnlineTask.ConfigureAwait(false); + } + } + + /// + public async Task OfflineInstance(Instance metadata, CancellationToken cancellationToken) + { + IInstance instance; + lock (this) + { + if (!instances.TryGetValue(metadata.Id, out instance)) + throw new InvalidOperationException("Instance not online!"); + instances.Remove(metadata.Id); + } + await instance.StopAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public async Task OnlineInstance(Instance metadata, CancellationToken cancellationToken) + { + var instance = instanceFactory.CreateInstance(metadata); + lock (this) + { + if (instances.ContainsKey(metadata.Id)) + throw new InvalidOperationException("Instance already online!"); + instances.Add(metadata.Id, instance); + } + await instance.StartAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + using(var scope = serviceProvider.CreateScope()) + { + var databaseContext = scope.ServiceProvider.GetRequiredService(); + await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); + var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); + var tasks = new List(); + await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); + } + } + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); + instances.Clear(); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 87626da1fc..ca8104ec21 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -11,6 +11,7 @@ using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Reflection; +using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Controllers; using Tgstation.Server.Host.Models; @@ -132,6 +133,10 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); From 0e091c35a135005e074c372e83029b9b200e55d2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Apr 2018 16:43:43 -0400 Subject: [PATCH 101/397] Wip idk --- .../Components/IByond.cs | 6 + src/Tgstation.Server.Host/Components/IChat.cs | 8 ++ .../Components/IConfiguration.cs | 6 + .../Components/IDreamDaemon.cs | 10 ++ .../Components/IDreamMaker.cs | 8 ++ .../Components/IInstance.cs | 13 +++ .../Components/IInstanceFactory.cs | 2 +- .../Components/IRepositoryManager.cs | 12 +- .../Components/Instance.cs | 45 ++++++++ .../Components/InstanceFactory.cs | 28 +++++ .../Components/InstanceManager.cs | 8 +- .../Components/Repository.cs | 16 ++- .../Components/RepositoryManager.cs | 106 +++++++++++++++++- src/Tgstation.Server.Host/Core/Application.cs | 4 +- 14 files changed, 254 insertions(+), 18 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IByond.cs create mode 100644 src/Tgstation.Server.Host/Components/IChat.cs create mode 100644 src/Tgstation.Server.Host/Components/IConfiguration.cs create mode 100644 src/Tgstation.Server.Host/Components/IDreamDaemon.cs create mode 100644 src/Tgstation.Server.Host/Components/IDreamMaker.cs create mode 100644 src/Tgstation.Server.Host/Components/Instance.cs create mode 100644 src/Tgstation.Server.Host/Components/InstanceFactory.cs diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByond.cs new file mode 100644 index 0000000000..9729da3ea3 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IByond.cs @@ -0,0 +1,6 @@ +namespace Tgstation.Server.Host.Components +{ + interface IByond + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/IChat.cs new file mode 100644 index 0000000000..a207605d0c --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IChat.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.Hosting; + +namespace Tgstation.Server.Host.Components +{ + interface IChat : IHostedService + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs new file mode 100644 index 0000000000..c0d4615989 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -0,0 +1,6 @@ +namespace Tgstation.Server.Host.Components +{ + interface IConfiguration + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs new file mode 100644 index 0000000000..9a9b37db20 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.Hosting; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + interface IDreamDaemon : IHostedService + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs new file mode 100644 index 0000000000..b5d9be9436 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.Hosting; + +namespace Tgstation.Server.Host.Components +{ + interface IDreamMaker : IHostedService + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index bdd3f392e5..d28a32d9a6 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -4,5 +4,18 @@ namespace Tgstation.Server.Host.Components { interface IInstance : IHostedService { + IRepositoryManager RepositoryManager { get; } + + IByond Byond { get; } + + IDreamMaker DreamMaker { get; } + + IDreamDaemon DreamDaemon { get; } + + IChat Chat { get; } + + IConfiguration Configuration { get; } + + void Rename(string newName); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index c76afabf4f..a31d69f2f4 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -4,6 +4,6 @@ namespace Tgstation.Server.Host.Components { interface IInstanceFactory { - IInstance CreateInstance(Instance metadata); + IInstance CreateInstance(Models.Instance metadata); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs index 63800c9386..f778e46798 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -1,4 +1,5 @@ -using System.Threading; +using Microsoft.Extensions.Hosting; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components @@ -6,7 +7,7 @@ namespace Tgstation.Server.Host.Components /// /// Factory for creating and loading s /// - interface IRepositoryManager + interface IRepositoryManager : IHostedService { /// /// Attempt to load the from the default location @@ -23,5 +24,12 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// The newly cloned Task CloneRepository(string url, string accessString, CancellationToken cancellationToken); + + /// + /// Change the interval in minutes at which the repository auto updates + /// + /// The new interval in minutes or to disable the auto update + /// A representing the running operation + Task SetAutoUpdateInterval(int? newInterval); } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs new file mode 100644 index 0000000000..e31e5177c6 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + sealed class Instance : IInstance + { + public IRepositoryManager RepositoryManager { get; } + + public IByond Byond { get; } + + public IDreamMaker DreamMaker { get; } + + public IDreamDaemon DreamDaemon { get; } + + public IChat Chat { get; } + + public IConfiguration Configuration { get; } + + readonly Api.Models.Instance metadata; + + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration) + { + this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); + RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); + Byond = byond ?? throw new ArgumentNullException(nameof(byond)); + DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)); + DreamDaemon = dreamDaemon ?? throw new ArgumentNullException(nameof(dreamDaemon)); + Chat = chat ?? throw new ArgumentNullException(nameof(chat)); + Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + } + + public void Rename(string newName) + { + if (String.IsNullOrWhiteSpace(newName)) + throw new ArgumentNullException(nameof(newName)); + metadata.Name = newName; + } + + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamMaker.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken)); + + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamMaker.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken)); + } +} diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs new file mode 100644 index 0000000000..3491c9e837 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -0,0 +1,28 @@ +using System; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class InstanceFactory : IInstanceFactory + { + readonly IIOManager ioManager; + + public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + /// + public IInstance CreateInstance(Models.Instance metadata) + { + //Create the ioManager for the instance + + var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path); + + //various other ioManagers + var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository"); + var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond"); + var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); + var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Static"); + + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 73477bbd76..92947c72a9 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -46,7 +46,7 @@ namespace Tgstation.Server.Host.Components } /// - public IInstance GetInstance(Instance metadata) + public IInstance GetInstance(Models.Instance metadata) { lock (this) { @@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task MoveInstance(Instance instance, string newPath, CancellationToken cancellationToken) + public async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken) { if (newPath == null) throw new ArgumentNullException(nameof(newPath)); @@ -83,7 +83,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OfflineInstance(Instance metadata, CancellationToken cancellationToken) + public async Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken) { IInstance instance; lock (this) @@ -96,7 +96,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OnlineInstance(Instance metadata, CancellationToken cancellationToken) + public async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken) { var instance = instanceFactory.CreateInstance(metadata); lock (this) diff --git a/src/Tgstation.Server.Host/Components/Repository.cs b/src/Tgstation.Server.Host/Components/Repository.cs index 994f71b249..20d8d569b4 100644 --- a/src/Tgstation.Server.Host/Components/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository.cs @@ -39,21 +39,31 @@ namespace Tgstation.Server.Host.Components /// readonly IIOManager ioMananger; + /// + /// to be taken when is called + /// + readonly Action onDispose; + /// /// Construct a /// /// The value of /// The value of - public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger) + /// The value if + public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, Action onDispose) { this.repository = repository ?? throw new ArgumentNullException(nameof(repository)); this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); + this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); IsGitHubRepository = Origin.ToUpperInvariant().Contains("GITHUB.COM"); } /// - public void Dispose() => repository.Dispose(); - + public void Dispose() + { + repository.Dispose(); + onDispose(); + } /// /// Convert to an "https://@{url} equivalent /// diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index 3080659d08..049396ba41 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -3,22 +3,83 @@ using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components { /// - sealed class RepositoryManager : IRepositoryManager + sealed class RepositoryManager : IRepositoryManager, IDisposable { /// /// The for the /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly RepositorySettings repositorySettings; + + /// + /// Used for controlling single access to the + /// + readonly SemaphoreSlim semaphore; + + /// + /// for + /// + CancellationTokenSource timerCancellationTokenSource; + + /// + /// Represents the running update timer if any + /// + Task currentTimerTask; + /// /// Construct a /// + /// The value of /// The value of - public RepositoryManager(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager) + { + this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + semaphore = new SemaphoreSlim(1); + } + + /// + public void Dispose() + { + timerCancellationTokenSource?.Dispose(); + semaphore.Dispose(); + } + + async Task StopTimer() + { + if (currentTimerTask == null) + return; + timerCancellationTokenSource.Cancel(); + await currentTimerTask.ConfigureAwait(false); + currentTimerTask = null; + } + + async Task TimerLoop(int minutes, string accessString, CancellationToken cancellationToken) + { + try + { + while (true) + { + await Task.Delay(TimeSpan.FromMinutes(minutes), cancellationToken).ConfigureAwait(false); + using (var repo = await LoadRepository(cancellationToken).ConfigureAwait(false)) + { + //TODO: Find the unauthorized exception, catch it, and log it + await repo.FetchOrigin(accessString, cancellationToken).ConfigureAwait(false); + await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException) { } + } /// public async Task CloneRepository(string url, string accessString, CancellationToken cancellationToken) @@ -47,10 +108,43 @@ namespace Tgstation.Server.Host.Components } /// - public Task LoadRepository(CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public async Task LoadRepository(CancellationToken cancellationToken) { - var repo = new LibGit2Sharp.Repository(ioManager.ResolvePath(".")); - return (IRepository)new Repository(repo, ioManager); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + LibGit2Sharp.Repository repo = null; + await Task.Factory.StartNew(() => + { + repo = new LibGit2Sharp.Repository(ioManager.ResolvePath(".")); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + return new Repository(repo, ioManager, () => semaphore.Release()); + } + + /// + public async Task SetAutoUpdateInterval(int? newInterval) + { + await StopTimer().ConfigureAwait(false); + if (!newInterval.HasValue) + return; + + string accessString = null; + + if (timerCancellationTokenSource != null) + timerCancellationTokenSource.Dispose(); + timerCancellationTokenSource = new CancellationTokenSource(); + + currentTimerTask = TimerLoop(repositorySettings.AutoUpdateInterval.Value, accessString, timerCancellationTokenSource.Token); + } + + /// + public Task StartAsync(CancellationToken cancellationToken) => SetAutoUpdateInterval(repositorySettings.AutoUpdateInterval); + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + var timerStopTask = StopTimer(); + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register(() => tcs.SetCanceled())) + await Task.WhenAny(timerStopTask, tcs.Task).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index ca8104ec21..cd65f38a98 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Core /// /// The for the /// - readonly IConfiguration configuration; + readonly Microsoft.Extensions.Configuration.IConfiguration configuration; /// /// The for the @@ -44,7 +44,7 @@ namespace Tgstation.Server.Host.Core /// /// The value of /// The value of - public Application(IConfiguration configuration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment) + public Application(Microsoft.Extensions.Configuration.IConfiguration configuration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment) { this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); From 507064ee140a7d221fd378aee0de5b876ecdad2f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Apr 2018 10:43:42 -0400 Subject: [PATCH 102/397] Add missing comments --- .../Components/RepositoryManager.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index 049396ba41..627ba44cfa 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -54,6 +54,10 @@ namespace Tgstation.Server.Host.Components semaphore.Dispose(); } + /// + /// Stops and joins it + /// + /// A representing the running operation async Task StopTimer() { if (currentTimerTask == null) @@ -62,7 +66,14 @@ namespace Tgstation.Server.Host.Components await currentTimerTask.ConfigureAwait(false); currentTimerTask = null; } - + + /// + /// Asyncronously fetch and reset the current branch for each given amount of + /// + /// The delay of the timer + /// The accessString to use for fetch operations + /// The for the operation + /// A representing the running operation async Task TimerLoop(int minutes, string accessString, CancellationToken cancellationToken) { try From fe88eef3f1bebe61823a727edddee73f189ee295 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Apr 2018 12:01:54 -0400 Subject: [PATCH 103/397] Doc comments and model tweaks --- .../Models/ChatSettings.cs | 12 +++++ src/Tgstation.Server.Api/Models/CompileJob.cs | 7 ++- .../Models/Configuration.cs | 27 +--------- .../Models/Internal/CompileJob.cs | 12 +++-- .../Internal/ConfigurationFileMetadata.cs | 33 ++++++++++++ .../Internal/DreamDaemonLaunchParameters.cs | 35 ++++++++++++ .../Models/Internal/DreamDaemonSettings.cs | 27 +--------- .../Models/Internal/DreamMakerSettings.cs | 4 +- .../Components/IByond.cs | 31 ++++++++++- src/Tgstation.Server.Host/Components/IChat.cs | 32 +++++++++++ .../Components/IConfiguration.cs | 52 +++++++++++++++++- .../Components/IDreamDaemon.cs | 53 +++++++++++++++++++ .../Components/IDreamMaker.cs | 13 +++++ .../Models/CompileJob.cs | 7 ++- 14 files changed, 284 insertions(+), 61 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs create mode 100644 src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs diff --git a/src/Tgstation.Server.Api/Models/ChatSettings.cs b/src/Tgstation.Server.Api/Models/ChatSettings.cs index a4cb033170..2900a7e055 100644 --- a/src/Tgstation.Server.Api/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/ChatSettings.cs @@ -6,6 +6,18 @@ namespace Tgstation.Server.Api.Models /// public sealed class ChatSettings : Internal.ChatSettings { + /// + /// If the IRC connection is established + /// + [Permissions(DenyWrite = true)] + bool IrcConnected { get; set; } + + /// + /// If the Discord connection is established + /// + [Permissions(DenyWrite = true)] + bool DiscordConnected { get; set; } + /// /// Channels the Discord bot should listen/announce in /// diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 2cc0f74988..a3b97ed6f7 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -7,7 +7,12 @@ /// The that triggered the job /// public User TriggeredBy { get; set; } - + + /// + /// The that cancelled the job if any + /// + public User CancelledBy { get; set; } + /// /// Git revision the compiler ran on. Not modifiable /// diff --git a/src/Tgstation.Server.Api/Models/Configuration.cs b/src/Tgstation.Server.Api/Models/Configuration.cs index 16b57be176..df3ceac5a2 100644 --- a/src/Tgstation.Server.Api/Models/Configuration.cs +++ b/src/Tgstation.Server.Api/Models/Configuration.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Api.Rights; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models { @@ -6,35 +6,12 @@ namespace Tgstation.Server.Api.Models /// Represents a static game file. Create and delete actions uncerimonuously overwrite/delete files /// #pragma warning disable CA1724 // System.Configuration name conflict - [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] - public sealed class Configuration + public sealed class Configuration : ConfigurationFileMetadata #pragma warning restore CA1724 // System.Configuration name conflict { - /// - /// The path to the file - /// - public string Path { get; set; } - - /// - /// If read access to the file was denied - /// - [Permissions(DenyWrite = true)] - public bool ReadDenied { get; set; } - - /// - /// If represents a directory. Will only be if is - /// - [Permissions(DenyWrite = true)] - public bool IsDirectory { get; set; } - /// /// The content of the file. Will be if is or during listing operations /// public byte[] Content { get; set; } - - /// - /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 - /// - public string LastReadHash { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index 9fccf5e173..add902e9cb 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -22,12 +22,18 @@ namespace Tgstation.Server.Api.Models.Internal /// /// When the compilation finished /// + [Required] public DateTimeOffset FinishedAt { get; set; } /// - /// If the compiler targeted the primary directory + /// The detected DMAPI version /// - public bool? TargetedPrimaryDirectory { get; set; } + public Version DMApiVersion { get; set; } + + /// + /// The .dme file used for compilation + /// + public string DmePath { get; set; } /// /// Textual output of DM @@ -35,7 +41,7 @@ namespace Tgstation.Server.Api.Models.Internal public string Output { get; set; } /// - /// Exit code of DM. If , the job was cancelled + /// Exit code of DM. If /// public int? ExitCode { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs b/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs new file mode 100644 index 0000000000..e402872f3d --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs @@ -0,0 +1,33 @@ +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + /// + /// Metadata about a file + /// + [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] + public class ConfigurationFileMetadata + { + /// + /// The path to the file + /// + public string Path { get; set; } + + /// + /// If read access to the file was denied + /// + [Permissions(DenyWrite = true)] + public bool ReadDenied { get; set; } + + /// + /// If represents a directory. Will only be if is + /// + [Permissions(DenyWrite = true)] + public bool IsDirectory { get; set; } + + /// + /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 + /// + public string LastReadHash { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs new file mode 100644 index 0000000000..8602b302b5 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -0,0 +1,35 @@ +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models.Internal +{ + /// + /// Launch settings for DreamDaemon + /// + [Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)] + public class DreamDaemonLaunchParameters + { + /// + /// If the BYOND web client can be used to connect to the game server + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] + public bool AllowWebClient { get; set; } + + /// + /// The level of + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] + public DreamDaemonSecurity SecurityLevel { get; set; } + + /// + /// The first port uses. This should be the publically advertised port + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] + public ushort PrimaryPort { get; set; } + + /// + /// The second port uses + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] + public ushort SecondaryPort { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index 6b0520c59e..b4fd279881 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -5,8 +5,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Configurable settings for /// - [Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)] - public class DreamDaemonSettings + public class DreamDaemonSettings : DreamDaemonLaunchParameters { /// /// If starts when it's starts @@ -14,12 +13,6 @@ namespace Tgstation.Server.Api.Models.Internal [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] public bool AutoStart { get; set; } - /// - /// If the BYOND web client can be used to connect to the game server - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] - public bool AllowWebClient { get; set; } - /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields /// @@ -31,23 +24,5 @@ namespace Tgstation.Server.Api.Models.Internal /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] public bool SoftShutdown { get; set; } - - /// - /// The level of - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] - public DreamDaemonSecurity SecurityLevel { get; set; } - - /// - /// The first port uses. This should be the publically advertised port - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort PrimaryPort { get; set; } - - /// - /// The second port uses - /// - [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort SecondaryPort { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs index 8d31ef3ef0..2e07ee683a 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs @@ -15,9 +15,9 @@ namespace Tgstation.Server.Api.Models.Internal public int? AutoCompileInterval { get; set; } /// - /// The .dme file tries to compile with + /// The .dme file tries to compile with without the extension /// [Permissions(WriteRight = DreamMakerRights.SetDme)] - public string TargetDme { get; set; } + public string ProjectName { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByond.cs index 9729da3ea3..ab3450872e 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByond.cs @@ -1,6 +1,35 @@ -namespace Tgstation.Server.Host.Components +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components { + /// + /// For managing the BYOND installation + /// interface IByond { + /// + /// Change the current BYOND version + /// + /// The new + /// The for the operation + Task ChangeVersion(Version version, CancellationToken cancellationToken); + + /// + /// Get the currently installed BYOND version + /// + /// The for the operation + /// The current BYOND version + Task GetVersion(CancellationToken cancellationToken); + + /// + /// Lock the current installation's location and run an + /// + /// A taking the path to either dm.exe or dreamdaemon.exe and returning a + /// Use the staged installation if possible + /// Pass the path of dreamdaemon.exe to if dm.exe otherwise + /// A representing the running + Task UseExecutable(Func operation, bool stagedIfExists, bool dreamDaemon); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/IChat.cs index a207605d0c..13fe8fa5dc 100644 --- a/src/Tgstation.Server.Host/Components/IChat.cs +++ b/src/Tgstation.Server.Host/Components/IChat.cs @@ -1,8 +1,40 @@ using Microsoft.Extensions.Hosting; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components { + /// + /// For managing connected chat services + /// interface IChat : IHostedService { + /// + /// If the IRC client is connected + /// + bool IrcConnected { get; } + + /// + /// If the Discord client is connected + /// + bool DiscordConnected { get; } + + /// + /// Change chat settings + /// + /// The new + /// The for the operation + /// A representing the running operation + Task ChangeSettings(ChatSettings newSettings, CancellationToken cancellationToken); + + /// + /// Change chat channels + /// + /// An of the new list of s + /// The for the operation + /// A representing the running operation + Task ChangeChannels(IEnumerable newChannels, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index c0d4615989..75bacfd240 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -1,6 +1,56 @@ -namespace Tgstation.Server.Host.Components +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Components { + /// + /// For managing the Configuration directory + /// interface IConfiguration { + /// + /// Copies all files in the CodeModifications directory to + /// + /// Path to the destination folder + /// A resultin in a of #include lines for the .dm files copied + Task> CopyDMFilesTo(string destination, CancellationToken cancellationToken); + + /// + /// Symlinks all directories in the GameData directory to + /// + /// Path to the destination folder + /// + /// A representing the running operation + Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken); + + /// + /// Get for all items in a given + /// + /// The relative path in the Configuration directory + /// The for the operation. If , the operation will be performed as the user of the + /// The for the operation + /// A resulting in the for the items in the directory + Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + + /// + /// + /// + /// The relative path in the Configuration directory + /// The for the operation. If , the operation will be performed as the user of the + /// The for the operation + /// A resulting in the of the file + Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + + /// + /// + /// + /// The relative path in the Configuration directory + /// The data to write + /// The for the operation. Usage may result in partial writes + /// A resulting in if the operation succeeded, if it failed due to permission errors + Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index 9a9b37db20..f5cf744187 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -1,10 +1,63 @@ using Microsoft.Extensions.Hosting; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components { + /// + /// For managing DreamDaemon + /// interface IDreamDaemon : IHostedService { + /// + /// If DreamDaemon is running + /// + bool Running { get; } + + /// + /// The port DreamDaemon is currently running on + /// + ushort? CurrentPort { get; } + + /// + /// The access token used for communication with the DMAPI + /// + string AccessToken { get; } + + /// + /// Launch DreamDaemon + /// + /// The for the launch + /// The for the operation + /// A representing the running operation + Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken); + + /// + /// Changes the if currently . Triggers a graceful restart + /// + /// The new + void ChangeSettings(DreamDaemonLaunchParameters launchParameters); + + /// + /// Restarts DreamDaemon + /// + /// If the restart will be delayed until a reboot is detected in the DMAPI and this function will retrun immediately. If the DMAPI isn't installed, this parameter is ignored + /// The for the operation + /// A representing the running operation + Task Restart(bool graceful, CancellationToken cancellationToken); + + /// + /// Terminates DreamDaemon + /// + /// If the termination will be delayed until a reboot is detected in the DMAPI and this function will return immediately. If the DMAPI isn't installed, this parameter is ignored + /// The for the operation + /// A representing the running operation + Task Terminate(bool graceful, CancellationToken cancellationToken); + + /// + /// Cancels any pending graceful reboots or terminations + /// + void CancelGracefulActions(); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index b5d9be9436..9f119ac27d 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -1,8 +1,21 @@ using Microsoft.Extensions.Hosting; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { + /// + /// For managing the compiler + /// interface IDreamMaker : IHostedService { + /// + /// Starts a compile + /// + /// The .dme file to use + /// The for the operation + /// A resulting in the partially populated for the operation + Task Compile(string dmePath, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 29bdac135c..a22e7ec5c3 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -8,13 +8,16 @@ namespace Tgstation.Server.Host.Models /// /// See /// - [Required] public User TriggeredBy { get; set; } + /// + /// See + /// + public User CancelledBy { get; set; } + /// /// See /// - [Required] public RevisionInformation RevisionInformation { get; set; } } } From fc606fa477b23a14e82a2606cf750ad2e8c3c5a7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Apr 2018 12:03:10 -0400 Subject: [PATCH 104/397] More comments --- src/Tgstation.Server.Host/Components/IConfiguration.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index 75bacfd240..dcf0734a60 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -36,7 +36,7 @@ namespace Tgstation.Server.Host.Components Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// - /// + /// Reads a given /// /// The relative path in the Configuration directory /// The for the operation. If , the operation will be performed as the user of the @@ -45,10 +45,10 @@ namespace Tgstation.Server.Host.Components Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// - /// + /// Writes to a given /// /// The relative path in the Configuration directory - /// The data to write + /// The data to write. If , the file is deleted /// The for the operation. Usage may result in partial writes /// A resulting in if the operation succeeded, if it failed due to permission errors Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken); From f50e981dc60b41963aa50fb38d5cc9634535c203 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Apr 2018 12:08:49 -0400 Subject: [PATCH 105/397] Add error logging to jobs --- .../Models/Internal/Job.cs | 9 +++++--- src/Tgstation.Server.Host/Core/JobManager.cs | 23 +++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index b8ebe63b70..205faa7707 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { @@ -19,14 +18,18 @@ namespace Tgstation.Server.Api.Models.Internal /// English description of the /// [Permissions(DenyWrite = true)] - [Required] public string Description { get; set; } + /// + /// Details of any exceptions caught during the + /// + [Permissions(DenyWrite = true)] + public string ExceptionDetails { get; set; } + /// /// When the was started /// [Permissions(DenyWrite = true)] - [Required] public DateTimeOffset StartedAt { get; set; } /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 94fceefca0..e81f8ece73 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -55,24 +55,23 @@ namespace Tgstation.Server.Host.Core /// A representing the running operation async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) { - bool cancelled; - try - { - await operation(cancellationToken).ConfigureAwait(false); - cancelled = false; - } - catch (OperationCanceledException) - { - cancelled = true; - } - using (var scope = serviceProvider.CreateScope()) { var databaseContext = scope.ServiceProvider.GetRequiredService(); job = new Job { Id = job.Id }; databaseContext.Jobs.Attach(job); - if (cancelled) + try + { + await operation(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { job.Cancelled = true; + } + catch (Exception e) + { + job.ExceptionDetails = e.ToString(); + } job.StoppedAt = DateTimeOffset.Now; await databaseContext.Save(default).ConfigureAwait(false); } From bb5d9e8d7a59ab6d37324d339efdc45d8e7beede Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Apr 2018 12:09:26 -0400 Subject: [PATCH 106/397] Ditch legacy config path --- src/Tgstation.Server.Host/Components/InstanceFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 3491c9e837..d003ad7051 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Components var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository"); var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond"); var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); - var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Static"); + var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); throw new NotImplementedException(); } From 36c1ac6508cc46a5c66deb400788c13741a1ece3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Apr 2018 13:18:12 -0400 Subject: [PATCH 107/397] Interfaces for the interface gods + other fixes --- .../Models/Configuration.cs | 2 +- .../Rights/DreamDaemonRights.cs | 6 +- .../Rights/DreamMakerRights.cs | 2 +- .../Components/ChatMessageEventArgs.cs | 15 +++ .../Components/ChatResponse.cs | 10 ++ .../Components/EventType.cs | 96 +++++++++++++++++++ src/Tgstation.Server.Host/Components/IChat.cs | 9 ++ .../Components/IConfiguration.cs | 4 +- .../Components/IEventConsumer.cs | 21 ++++ .../Components/IInterop.cs | 26 +++++ .../Components/ServerControlEventArgs.cs | 25 +++++ 11 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/ChatMessageEventArgs.cs create mode 100644 src/Tgstation.Server.Host/Components/ChatResponse.cs create mode 100644 src/Tgstation.Server.Host/Components/EventType.cs create mode 100644 src/Tgstation.Server.Host/Components/IEventConsumer.cs create mode 100644 src/Tgstation.Server.Host/Components/IInterop.cs create mode 100644 src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs diff --git a/src/Tgstation.Server.Api/Models/Configuration.cs b/src/Tgstation.Server.Api/Models/Configuration.cs index df3ceac5a2..26f99815c9 100644 --- a/src/Tgstation.Server.Api/Models/Configuration.cs +++ b/src/Tgstation.Server.Api/Models/Configuration.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Api.Models #pragma warning restore CA1724 // System.Configuration name conflict { /// - /// The content of the file. Will be if is or during listing operations + /// The content of the file. Will be if is or during listing operations /// public byte[] Content { get; set; } } diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index f99f63cd58..6068b118ed 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -25,15 +25,15 @@ namespace Tgstation.Server.Api.Rights /// SetAutoStart = 4, /// - /// User set + /// User set /// SetSecurity = 8, /// - /// User can read all ports, , , , , and + /// User can read all ports, , , , , and /// ReadMetadata = 16, /// - /// User can change + /// User can change /// SetWebClient = 32, /// diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index 809816dbf9..661530c95a 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Rights /// SetAutoCompile = 8, /// - /// User may modify + /// User may modify /// SetDme = 16 } diff --git a/src/Tgstation.Server.Host/Components/ChatMessageEventArgs.cs b/src/Tgstation.Server.Host/Components/ChatMessageEventArgs.cs new file mode 100644 index 0000000000..7e5839565a --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ChatMessageEventArgs.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents a chat broadcast request + /// + sealed class ChatMessageEventArgs : EventArgs + { + /// + /// The to send out + /// + public ChatResponse ChatResponse { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/ChatResponse.cs b/src/Tgstation.Server.Host/Components/ChatResponse.cs new file mode 100644 index 0000000000..d79f8f4a7f --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ChatResponse.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Tgstation.Server.Host.Components +{ + sealed class ChatResponse + { + public string Message { get; set; } + public List ChannelIds { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs new file mode 100644 index 0000000000..5be6d4617f --- /dev/null +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -0,0 +1,96 @@ +namespace Tgstation.Server.Host.Components +{ + /// + /// Types of events + /// + enum EventType + { + /// + /// Parameters: Reference name, commit sha + /// + RepoResetOrigin, + /// + /// Parameters: Reference name, commit sha + /// + RepoCheckout, + /// + /// No parameters + /// + RepoFetch, + /// + /// Parameters: Comma separated list in form of "#{Pull Request Number} @ {7 character SHA} + /// + RepoMergePullRequests, + + /// + /// Parameters: Current version, new version + /// + ByondChangeStart, + /// + /// No parameters + /// + ByondChangeCancelled, + /// + /// Parameters: Error string + /// + ByondFail, + /// + /// No parameters + /// + ByondStageComplete, + /// + /// No parameters + /// + ByondChangeComplete, + + /// + /// Parameters: Commit sha, parameter of + /// + CompileStart, + /// + /// No parameters + /// + CompileCancelled, + /// + /// Parameters: Error string + /// + CompileFailure, + /// + /// No parameters + /// + CompileComplete, + + /// + /// Parameters: Access token + /// + DDLaunched, + /// + /// Parameters: Exit code + /// + DDCrash, + /// + /// No parameters + /// + DDExit, + /// + /// No parameters + /// + DDRestart, + /// + /// No parameters + /// + DDBeginGracefulRestart, + /// + /// No parameters + /// + DDBeginGracefulShutdown, + /// + /// No parameters + /// + DDCancelGraceful, + /// + /// No parameters + /// + DDTerminated, + } +} diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/IChat.cs index 13fe8fa5dc..9ebd0877c2 100644 --- a/src/Tgstation.Server.Host/Components/IChat.cs +++ b/src/Tgstation.Server.Host/Components/IChat.cs @@ -36,5 +36,14 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// A representing the running operation Task ChangeChannels(IEnumerable newChannels, CancellationToken cancellationToken); + + /// + /// Send a chat to a given set of + /// + /// The message being sent + /// The s of the s to send to + /// The for the operation + /// A representing the running operation + Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index dcf0734a60..3a6f2f0ded 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -15,6 +15,7 @@ namespace Tgstation.Server.Host.Components /// Copies all files in the CodeModifications directory to /// /// Path to the destination folder + /// The for the operation /// A resultin in a of #include lines for the .dm files copied Task> CopyDMFilesTo(string destination, CancellationToken cancellationToken); @@ -22,7 +23,7 @@ namespace Tgstation.Server.Host.Components /// Symlinks all directories in the GameData directory to /// /// Path to the destination folder - /// + /// The for the operation /// A representing the running operation Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken); @@ -48,6 +49,7 @@ namespace Tgstation.Server.Host.Components /// Writes to a given /// /// The relative path in the Configuration directory + /// The for the operation. If , the operation will be performed as the user of the /// The data to write. If , the file is deleted /// The for the operation. Usage may result in partial writes /// A resulting in if the operation succeeded, if it failed due to permission errors diff --git a/src/Tgstation.Server.Host/Components/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/IEventConsumer.cs new file mode 100644 index 0000000000..322860c7b3 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IEventConsumer.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Consumes s and takes the appropriate actions + /// + interface IEventConsumer + { + /// + /// Handle a given + /// + /// The + /// The parameters for + /// The for the operation + /// A representing the running operation + Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs new file mode 100644 index 0000000000..7c1d0e67ad --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInterop.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Handles communication with the DMAPI + /// + interface IInterop + { + event EventHandler OnServerControl; + event EventHandler OnChatMessage; + + Version MinimumApiVersion { get; } + Version MaximumApiVersion { get; } + + Task GetApiVersion(CancellationToken cancellationToken); + + void SetActivePort(ushort? port); + + Task ChatCommand(string command, string arguments, CancellationToken cancellationToken); + + Task PromptChatResponse(EventType eventType, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs new file mode 100644 index 0000000000..a0c12a243f --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs @@ -0,0 +1,25 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents a DreamDaemon control request or notification + /// + sealed class ServerControlEventArgs : EventArgs + { + /// + /// If the event will result in a new run of the world + /// + bool ServerReboot { get; set; } + + /// + /// If the event will result in a restart of the process. If both this and are , the server is being terminated + /// + bool ProcessRestart { get; set; } + + /// + /// If the action to be taken is graceful + /// + bool Graceful { get; set; } + } +} \ No newline at end of file From fff7d1b10a9e69efd293d4fc1003ac79763d463e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Apr 2018 15:04:13 -0400 Subject: [PATCH 108/397] WIP as usual --- .../Models/DreamDaemon.cs | 2 +- .../Components/DreamDaemon.cs | 276 ++++++++++++++++++ .../Components/IByond.cs | 7 + .../Components/IDreamDaemon.cs | 20 +- .../Components/IInstanceManager.cs | 2 +- .../Components/IInstanceShutdownMethod.cs | 11 + .../Components/IInterop.cs | 6 +- .../Components/InstanceManager.cs | 3 + .../Components/ServerControlEventArgs.cs | 8 +- .../Security/CryptographySuite.cs | 3 + .../Security/ICryptographySuite.cs | 6 + src/Tgstation.Server.Host/Server.cs | 4 +- 12 files changed, 336 insertions(+), 12 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemon.cs create mode 100644 src/Tgstation.Server.Host/Components/IInstanceShutdownMethod.cs diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 49571a3fa3..4a332b36dd 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Api.Models /// The current status of /// [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] - public DreamDaemonStatus? Status { get; set; } + public bool? Running { get; set; } /// /// The current of diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs new file mode 100644 index 0000000000..6ca1a8fc6b --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -0,0 +1,276 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class DreamDaemon : IDreamDaemon, IDisposable + { + /// + public bool Running { get; private set; } + + /// + public ushort? CurrentPort { get; private set; } + + /// + public string AccessToken { get; private set; } + + /// + public DreamDaemonSecurity? CurrentSecurity { get; private set; } + + /// + public bool SoftRebooting { get; private set; } + + /// + public bool SoftStopping { get; private set; } + + /// + /// The for + /// + readonly IEventConsumer eventConsumer; + /// + /// The for + /// + readonly IByond byond; + /// + /// The for + /// + readonly ICryptographySuite cryptographySuite; + /// + /// The for + /// + readonly IInterop interop; + /// + /// The for + /// + readonly IInstanceShutdownMethod instanceShutdownMethod; + readonly IIOManager ioManager; + + readonly bool autoStart; + + readonly SemaphoreSlim semaphore; + + DreamDaemonLaunchParameters currentLaunchParameters; + + CancellationTokenSource watchdogCancellationTokenSource; + Task watchdogTask; + + public DreamDaemon(IEventConsumer eventConsumer, IByond byond, ICryptographySuite cryptographySuite, IInterop interop, IInstanceShutdownMethod instanceShutdownMethod, DreamDaemonSettings initialSettings) + { + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); + this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); + + interop.SetServerControlHandler(OnServerControl); + + currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); + autoStart = initialSettings.AutoStart; + + semaphore = new SemaphoreSlim(1); + } + + public void Dispose() + { + if (watchdogCancellationTokenSource != null) + watchdogCancellationTokenSource.Dispose(); + semaphore.Dispose(); + } + + Task OnServerControl(ServerControlEventArgs serverControlEventArgs, CancellationToken cancellationToken) + { + lock (this) + { + CancelGracefulActions(); + if (serverControlEventArgs.ProcessRestart) + return Restart(serverControlEventArgs.Graceful, cancellationToken); + if(!serverControlEventArgs.ServerReboot) + return Terminate(serverControlEventArgs.Graceful, cancellationToken); + return Task.CompletedTask; + } + } + + static string SecurityWord(DreamDaemonSecurity securityLevel) + { + switch (securityLevel) + { + case DreamDaemonSecurity.Safe: + return "safe"; + case DreamDaemonSecurity.Trusted: + return "trusted"; + case DreamDaemonSecurity.Ultrasafe: + return "ultrasafe"; + default: + throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel)); + } + } + + async Task Watchdog(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) + { + async Task RunOnce(string executablePath) + { + if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null) + throw new InvalidOperationException("No byond version installed!"); + + await byond.ClearCache(cancellationToken).ConfigureAwait(false); + using (var proc = new Process()) + { + var accessToken = cryptographySuite.GetSecureString(); + proc.StartInfo.FileName = executablePath; + proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {5}-close -verbose -params \"server_service={3}&server_service_version={4}&{6}={7}\" -{2} -public", DMB, launchParameters.PrimaryPort, SecurityWord(launchParameters.SecurityLevel), accessToken, Application.Version, launchParameters.AllowWebClient ? "-webclient " : String.Empty); + + proc.EnableRaisingEvents = true; + var tcs = new TaskCompletionSource(); + proc.Exited += (a, b) => tcs.SetResult(null); + + try + { + proc.Start(); + + await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + + if (onSuccessfulStartup != null) + { + onSuccessfulStartup.SetResult(null); + onSuccessfulStartup = null; + } + + try + { + using (cancellationToken.Register(() => tcs.SetCanceled())) + await tcs.Task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + return; + } + finally + { + if (!instanceShutdownMethod.GracefulShutdown) + { + proc.Kill(); + proc.WaitForExit(); + } + } + } + catch (Exception e) + { + onSuccessfulStartup?.SetException(e); + throw; + } + await eventConsumer.HandleEvent(proc.ExitCode != 0 ? EventType.DDCrash : EventType.DDExit, null, cancellationToken).ConfigureAwait(false); + } + }; + + do + { + await byond.UseExecutable(RunOnce, false, true).ConfigureAwait(false); + } while (!cancellationToken.IsCancellationRequested); + } + + public void CancelGracefulActions() + { + lock (this) + { + SoftRebooting = false; + SoftStopping = false; + } + } + + public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) + { + Task launchTask; + lock (this) + { + currentLaunchParameters = launchParameters; + if (!Running) + launchTask = Launch(launchParameters, cancellationToken); + else + launchTask = Restart(true, cancellationToken); + } + await launchTask.ConfigureAwait(false); + } + + public async Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) + { + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + if (Running) + throw new InvalidOperationException("DreamDaemon already running!"); + Running = true; + await eventConsumer.HandleEvent(EventType.DDLaunched, null, cancellationToken).ConfigureAwait(false); + watchdogCancellationTokenSource?.Dispose(); + watchdogCancellationTokenSource = new CancellationTokenSource(); + var startupTcs = new TaskCompletionSource(); + watchdogTask = Watchdog(currentLaunchParameters, startupTcs, watchdogCancellationTokenSource.Token); + await startupTcs.Task.ConfigureAwait(false); + } + finally + { + semaphore.Release(); + } + } + + + public async Task Restart(bool graceful, CancellationToken cancellationToken) + { + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + if (graceful) + { + if (!Running) + throw new InvalidOperationException("DreamDaemon not running!"); + if (SoftStopping) + throw new InvalidOperationException("DreamDaemon has a graceful stop queued!"); + SoftRebooting = true; + return; + } + await eventConsumer.HandleEvent(EventType.DDRestart, null, cancellationToken).ConfigureAwait(false); + if (Running) + watchdogCancellationTokenSource.Cancel(); + await Launch(currentLaunchParameters, cancellationToken).ConfigureAwait(false); + } + finally + { + semaphore.Release(); + } + } + + public Task StartAsync(CancellationToken cancellationToken) => autoStart ? Launch(currentLaunchParameters, cancellationToken) : Task.CompletedTask; + + public Task StopAsync(CancellationToken cancellationToken) => Terminate(false, cancellationToken); + + public async Task Terminate(bool graceful, CancellationToken cancellationToken) + { + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + if (graceful) + { + if (!Running) + throw new InvalidOperationException("DreamDaemon not running!"); + SoftRebooting = false; + SoftStopping = true; + return; + } + await eventConsumer.HandleEvent(EventType.DDTerminated, null, cancellationToken).ConfigureAwait(false); + watchdogCancellationTokenSource.Cancel(); + await watchdogTask.ConfigureAwait(false); + } + finally + { + semaphore.Release(); + } + } + } +} diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByond.cs index ab3450872e..7f9d0ce98f 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByond.cs @@ -31,5 +31,12 @@ namespace Tgstation.Server.Host.Components /// Pass the path of dreamdaemon.exe to if dm.exe otherwise /// A representing the running Task UseExecutable(Func operation, bool stagedIfExists, bool dreamDaemon); + + /// + /// Clears the cache folder + /// + /// The for the operation + /// A representing the running operation + Task ClearCache(CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index f5cf744187..b49ac50e4c 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Hosting; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components @@ -15,11 +16,26 @@ namespace Tgstation.Server.Host.Components /// bool Running { get; } + /// + /// If DreamDaemon is going to soft reboot + /// + bool SoftRebooting { get; } + + /// + /// If DreamDaemon is going to soft stop + /// + bool SoftStopping { get; } + /// /// The port DreamDaemon is currently running on /// ushort? CurrentPort { get; } + /// + /// The current of + /// + DreamDaemonSecurity? CurrentSecurity { get; } + /// /// The access token used for communication with the DMAPI /// @@ -37,7 +53,9 @@ namespace Tgstation.Server.Host.Components /// Changes the if currently . Triggers a graceful restart /// /// The new - void ChangeSettings(DreamDaemonLaunchParameters launchParameters); + /// The for the operation + /// A representing the running operation + Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken); /// /// Restarts DreamDaemon diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index d63384a3fd..356a2fd9c8 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing s /// - interface IInstanceManager + interface IInstanceManager : IInstanceShutdownMethod { /// /// Get the associated with given diff --git a/src/Tgstation.Server.Host/Components/IInstanceShutdownMethod.cs b/src/Tgstation.Server.Host/Components/IInstanceShutdownMethod.cs new file mode 100644 index 0000000000..14ced74d67 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInstanceShutdownMethod.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tgstation.Server.Host.Components +{ + interface IInstanceShutdownMethod + { + bool GracefulShutdown { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs index 7c1d0e67ad..bc42312136 100644 --- a/src/Tgstation.Server.Host/Components/IInterop.cs +++ b/src/Tgstation.Server.Host/Components/IInterop.cs @@ -9,15 +9,15 @@ namespace Tgstation.Server.Host.Components /// interface IInterop { - event EventHandler OnServerControl; - event EventHandler OnChatMessage; + void SetServerControlHandler(Func serverControlHandler); + void SetChatMessageHandler(Func chatMessageHandler); Version MinimumApiVersion { get; } Version MaximumApiVersion { get; } Task GetApiVersion(CancellationToken cancellationToken); - void SetActivePort(ushort? port); + void SetRun(ushort? port, string accessToken); Task ChatCommand(string command, string arguments, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 92947c72a9..ae48802658 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -14,6 +14,9 @@ namespace Tgstation.Server.Host.Components /// sealed class InstanceManager : IInstanceManager, IHostedService { + /// + public bool GracefulShutdown { get; set; } + /// /// The for the /// diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs index a0c12a243f..ba9b7b2bef 100644 --- a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs +++ b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs @@ -10,16 +10,16 @@ namespace Tgstation.Server.Host.Components /// /// If the event will result in a new run of the world /// - bool ServerReboot { get; set; } + public bool ServerReboot { get; set; } /// /// If the event will result in a restart of the process. If both this and are , the server is being terminated /// - bool ProcessRestart { get; set; } - + public bool ProcessRestart { get; set; } + /// /// If the action to be taken is graceful /// - bool Graceful { get; set; } + public bool Graceful { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs index a3c5553030..b0f68e7a5c 100644 --- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -56,5 +56,8 @@ namespace Tgstation.Server.Host.Security } return true; } + + /// + public string GetSecureString() => Convert.ToBase64String(GetSecureBytes(30)); } } diff --git a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs index b19f805871..137cbf54ff 100644 --- a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs @@ -21,5 +21,11 @@ namespace Tgstation.Server.Host.Security /// The password to check /// if matches the hash, otherwise bool CheckUserPassword(User user, string password); + + /// + /// Generates a 40-length secure ascii + /// + /// A 40-length secure ascii + string GetSecureString(); } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 506424c49c..6d73783284 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -10,7 +10,7 @@ using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host { /// - sealed class Server : IServer, IServerUpdateConsumer + sealed class Server : IServer, IServerControl { /// public string UpdatePath { get; private set; } @@ -39,7 +39,7 @@ namespace Tgstation.Server.Host using (cancellationTokenSource) using (var webHost = webHostBuilder .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) await webHost.RunAsync(cancellationToken).ConfigureAwait(false); From e929253a0f21c2543f63b422b79094c8bcbed381 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Apr 2018 10:48:40 -0400 Subject: [PATCH 109/397] More stuff --- .../Models/Internal/CompileJob.cs | 5 + .../Components/DreamDaemon.cs | 107 +++++++---------- .../Components/DreamDaemonExecutor.cs | 110 ++++++++++++++++++ .../Components/DreamDaemonParameters.cs | 11 ++ .../Components/IDreamDaemonExecutor.cs | 25 ++++ .../Components/ServerControlEventArgs.cs | 2 + 6 files changed, 194 insertions(+), 66 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs create mode 100644 src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index add902e9cb..d31366048f 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -40,6 +40,11 @@ namespace Tgstation.Server.Api.Models.Internal /// public string Output { get; set; } + /// + /// The Game folder the results were compiled into + /// + public Guid? OutputGuid { get; set; } + /// /// Exit code of DM. If /// diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index 6ca1a8fc6b..15926c884d 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -1,6 +1,4 @@ using System; -using System.Diagnostics; -using System.Globalization; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -51,12 +49,22 @@ namespace Tgstation.Server.Host.Components /// The for /// readonly IInstanceShutdownMethod instanceShutdownMethod; + /// + /// The for + /// readonly IIOManager ioManager; + /// + /// The for + /// + readonly IDreamDaemonExecutor dreamDaemonExecutor; + + /// + /// Used for write control to class variables + /// + readonly SemaphoreSlim semaphore; readonly bool autoStart; - readonly SemaphoreSlim semaphore; - DreamDaemonLaunchParameters currentLaunchParameters; CancellationTokenSource watchdogCancellationTokenSource; @@ -78,6 +86,7 @@ namespace Tgstation.Server.Host.Components semaphore = new SemaphoreSlim(1); } + /// public void Dispose() { if (watchdogCancellationTokenSource != null) @@ -97,22 +106,7 @@ namespace Tgstation.Server.Host.Components return Task.CompletedTask; } } - - static string SecurityWord(DreamDaemonSecurity securityLevel) - { - switch (securityLevel) - { - case DreamDaemonSecurity.Safe: - return "safe"; - case DreamDaemonSecurity.Trusted: - return "trusted"; - case DreamDaemonSecurity.Ultrasafe: - return "ultrasafe"; - default: - throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel)); - } - } - + async Task Watchdog(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) { async Task RunOnce(string executablePath) @@ -121,53 +115,28 @@ namespace Tgstation.Server.Host.Components throw new InvalidOperationException("No byond version installed!"); await byond.ClearCache(cancellationToken).ConfigureAwait(false); - using (var proc = new Process()) + + string dmb = null; //TODO + var accessToken = cryptographySuite.GetSecureString(); + var usePrimaryPort = true; + + var ddTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, executablePath, dmb, accessToken, usePrimaryPort, cancellationToken); + + await onSuccessfulStartup.Task.ConfigureAwait(false); + + interop.SetRun(usePrimaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken); + + int ddExitCode; + try { - var accessToken = cryptographySuite.GetSecureString(); - proc.StartInfo.FileName = executablePath; - proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {5}-close -verbose -params \"server_service={3}&server_service_version={4}&{6}={7}\" -{2} -public", DMB, launchParameters.PrimaryPort, SecurityWord(launchParameters.SecurityLevel), accessToken, Application.Version, launchParameters.AllowWebClient ? "-webclient " : String.Empty); - - proc.EnableRaisingEvents = true; - var tcs = new TaskCompletionSource(); - proc.Exited += (a, b) => tcs.SetResult(null); - - try - { - proc.Start(); - - await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - - if (onSuccessfulStartup != null) - { - onSuccessfulStartup.SetResult(null); - onSuccessfulStartup = null; - } - - try - { - using (cancellationToken.Register(() => tcs.SetCanceled())) - await tcs.Task.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - return; - } - finally - { - if (!instanceShutdownMethod.GracefulShutdown) - { - proc.Kill(); - proc.WaitForExit(); - } - } - } - catch (Exception e) - { - onSuccessfulStartup?.SetException(e); - throw; - } - await eventConsumer.HandleEvent(proc.ExitCode != 0 ? EventType.DDCrash : EventType.DDExit, null, cancellationToken).ConfigureAwait(false); + ddExitCode = await ddTask.ConfigureAwait(false); } + finally + { + interop.SetRun(null, null); + } + + await eventConsumer.HandleEvent(ddExitCode != 0 ? EventType.DDCrash : EventType.DDExit, null, cancellationToken).ConfigureAwait(false); }; do @@ -176,6 +145,7 @@ namespace Tgstation.Server.Host.Components } while (!cancellationToken.IsCancellationRequested); } + /// public void CancelGracefulActions() { lock (this) @@ -185,6 +155,7 @@ namespace Tgstation.Server.Host.Components } } + /// public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { Task launchTask; @@ -199,6 +170,7 @@ namespace Tgstation.Server.Host.Components await launchTask.ConfigureAwait(false); } + /// public async Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -220,7 +192,7 @@ namespace Tgstation.Server.Host.Components } } - + /// public async Task Restart(bool graceful, CancellationToken cancellationToken) { await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -246,10 +218,13 @@ namespace Tgstation.Server.Host.Components } } + /// public Task StartAsync(CancellationToken cancellationToken) => autoStart ? Launch(currentLaunchParameters, cancellationToken) : Task.CompletedTask; + /// public Task StopAsync(CancellationToken cancellationToken) => Terminate(false, cancellationToken); + /// public async Task Terminate(bool graceful, CancellationToken cancellationToken) { await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs new file mode 100644 index 0000000000..56dc49d5e9 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -0,0 +1,110 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class DreamDaemonExecutor : IDreamDaemonExecutor + { + /// + /// Change a given into the appropriate DreamDaemon command line word + /// + /// The level to change + /// A representation of the command line parameter + static string SecurityWord(DreamDaemonSecurity securityLevel) + { + switch (securityLevel) + { + case DreamDaemonSecurity.Safe: + return "safe"; + case DreamDaemonSecurity.Trusted: + return "trusted"; + case DreamDaemonSecurity.Ultrasafe: + return "ultrasafe"; + default: + throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel)); + } + } + + /// + /// The for the + /// + readonly IInstanceShutdownMethod instanceShutdownMethod; + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// Construct a + /// + /// The value of + /// The value of + public DreamDaemonExecutor(IInstanceShutdownMethod instanceShutdownMethod, IIOManager ioManager) + { + this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + } + + /// + public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken) + { + using (var proc = new Process()) + { + proc.StartInfo.FileName = dreamDaemonPath; + proc.StartInfo.WorkingDirectory = ioManager.GetDirectoryName(dmbPath); + proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}&{8}={9}&{10}={11}&{12}={13}\"", dmbPath, usePrimaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel), + DreamDaemonParameters.AccessToken, accessToken, + DreamDaemonParameters.HostVersion, Application.Version, + DreamDaemonParameters.IsSecondaryServer, !usePrimaryPort, + DreamDaemonParameters.PrimaryPort, launchParameters.PrimaryPort, + DreamDaemonParameters.SecondaryPort, launchParameters.SecondaryPort); + + proc.EnableRaisingEvents = true; + var tcs = new TaskCompletionSource(); + proc.Exited += (a, b) => tcs.SetResult(null); + + try + { + proc.Start(); + + await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + + if (onSuccessfulStartup != null) + { + onSuccessfulStartup.SetResult(null); + onSuccessfulStartup = null; + } + + try + { + using (cancellationToken.Register(() => tcs.SetCanceled())) + await tcs.Task.ConfigureAwait(false); + } + finally + { + if (!instanceShutdownMethod.GracefulShutdown) + { + proc.Kill(); + proc.WaitForExit(); + } + } + + return proc.ExitCode; + } + catch (Exception e) + { + onSuccessfulStartup.SetException(e); + throw; + } + } + } + + } +} diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs new file mode 100644 index 0000000000..e6b409730d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs @@ -0,0 +1,11 @@ +namespace Tgstation.Server.Host.Components +{ + static class DreamDaemonParameters + { + public const string AccessToken = "tgs_key"; + public const string HostVersion = "tgs_ver"; + public const string PrimaryPort = "tgs_port1"; + public const string SecondaryPort = "tgs_port2"; + public const string IsSecondaryServer = "tgs_second"; + } +} diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs new file mode 100644 index 0000000000..5ed6534bbf --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components +{ + /// + /// For running a DreamDaemon instance + /// + interface IDreamDaemonExecutor + { + /// + /// Run a dream daemon instance + /// + /// The + /// The that is completed when dream daemon starts without crashing + /// The path to the dreamdaemon executable + /// The path to the .dmb to run, the working directory will be derived from this + /// The access token to be used for communication + /// If the server should open on or of + /// The for the operation + /// A representing the lifetime of the process and resulting in the exit code + Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs index ba9b7b2bef..74275114bc 100644 --- a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs +++ b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs @@ -7,6 +7,8 @@ namespace Tgstation.Server.Host.Components /// sealed class ServerControlEventArgs : EventArgs { + public bool PrimeReboot { get; set; } + /// /// If the event will result in a new run of the world /// From b42a307840cbadbe1fb2fcb1be3be4c307419016 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Apr 2018 11:45:24 -0400 Subject: [PATCH 110/397] Work on the DMAPI --- src/DMAPI/tgs.dm | 149 +++++++++++++++++++++++++++++++++ src/DMAPI/tgs/tgs_commands.dm | 76 +++++++++++++++++ src/DMAPI/tgs/tgs_interface.dm | 135 +++++++++++++++++++++++++++++ tgstation-server.sln | 14 ++++ 4 files changed, 374 insertions(+) create mode 100644 src/DMAPI/tgs.dm create mode 100644 src/DMAPI/tgs/tgs_commands.dm create mode 100644 src/DMAPI/tgs/tgs_interface.dm diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm new file mode 100644 index 0000000000..d19f2d3a58 --- /dev/null +++ b/src/DMAPI/tgs.dm @@ -0,0 +1,149 @@ +// /tg/station 13 server tools DMAPI + +//CONFIGURATION + +//use this define if you want to do configuration outside of this file +#ifndef TGS_EXTERNAL_CONFIGURATION + +//Comment this out once you've filled in the below +#error /tg/station server tools interface unconfigured + +//Required interfaces (fill in with your codebase equivalent): + +//create a global variable named `Name` and set it to `Value` +//These globals must not be modifiable from anywhere outside of the server tools +#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) + +//Read the value in the global variable `Name` +#define TGS_READ_GLOBAL(Name) + +//Set the value in the global variable `Name` to `Value` +#define TGS_WRITE_GLOBAL(Name, Value) + +//Disallow ANYONE from reflecting a given path, security measure to prevent in-game priveledge escalation +#define TGS_PROTECT_DATUM(Path) + +//display an announcement `message` from the server to all players +#define TGS_WORLD_ANNOUNCE(message) + +//Write a string `message` to a server log +#define TGS_LOG(message) + +//Notify current in-game administrators of a string `event` +#define TGS_NOTIFY_ADMINS(event) + +#endif + +//REQUIRED HOOKS + +//Call this somewhere in /world/New() that is always run +/world/proc/TgsNew() + return + +//Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified +#define TGS_TOPIC var/tgs_topic_return = TgsCommand(params2list(T)); if(tgs_topic_return) return tgs_topic_return + +//Call this at the beginning of world/Reboot(reason) +/world/proc/TgsReboot() + return + +//DATUM DEFINITIONS + +//represents git revision information about the current world build +/datum/tgs_revision_information + 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 merge of a GitHub pull request +/datum/tgs_revision_information/test_merge + var/number //pull request number + var/body //pull request body + var/author //pull request github author + var/url //link to pull request html + var/pull_request_commit //commit of the pull request when it was merged + var/time_merged //timestamp of when the merge commit for the pull request was created + var/comment //optional comment left by the one who initiated the test merge + +//Gets a list of active `/datum/tgs_revision_information/test_merge`s +/world/proc/TgsGetTestMerges() + return + +/datum/tgs_chat_channel + var/id //internal channel representation + var/friendly_name //user friendly channel name + var/server_name //server name the channel resides on + var/provider_name //chat provider for the channel + var/is_admin_channel //if the server operator has marked this channel for game admins only + +/datum/tgs_chat_user + var/id //Internal user representation + var/friendly_name //The user's public name + var/datum/tgs_chat_channel //The /datum/tgs_chat_channel this user was from + var/mention //The text to use to ping this user in a message + +//FUNCTIONS + +//Returns the respective string version of the API +/world/proc/TgsMaximumAPIVersion() + return + +/world/proc/TgsMinimumAPIVersion() + 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 version of the service running the server +/world/proc/TgsVersion() + return + +//Forces a hard reboot of BYOND by ending the process +//unlike del(world) clients will try to reconnect +//If the service has not requested a shutdown, the next server will take over +/world/proc/TgsEndProcess(silent) + return + +//Gets a list of connected tgs_chat_channel +/world/proc/TgsChatChannelInfo() + return + +//Sends a message to connected game chats +//message: The message to send +//channels: optional channels to limit the broadcast to +/world/proc/TgsChatBroadcast(message, list/channels) + return + +//Send a message to non-admin connected chats +//message: The message to send +//admin_only: If TRUE, message will instead be sent to only admin connected chats +/world/proc/TgsTargetedChatBroadcast(message, admin_only) + return + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/src/DMAPI/tgs/tgs_commands.dm b/src/DMAPI/tgs/tgs_commands.dm new file mode 100644 index 0000000000..ce3fb1bb75 --- /dev/null +++ b/src/DMAPI/tgs/tgs_commands.dm @@ -0,0 +1,76 @@ +/datum/server_tools_command + var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command + var/help_text = "" //help text for this command + var/required_parameters = 0 //number of parameters required for this command + var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins + +//override to implement command +//sender is the display name of who sent the command +//params is the trimmed string following the command name +/datum/server_tools_command/proc/Run(sender, params) + CRASH("[type] has no implementation for Run()") + +/world/proc/ListServiceCustomCommands(warnings_only) + if(!warnings_only) + . = list() + var/list/command_name_types = list() + var/list/warned_command_names = warnings_only ? list() : null + for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command) + var/datum/server_tools_command/stc = I + var/command_name = initial(stc.name) + var/static/list/warned_server_tools_names = list() + if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) + if(warnings_only && !warned_command_names[command_name]) + SERVER_TOOLS_LOG("WARNING: Custom command [command_name] can't be used as it is empty or contains illegal characters!") + warned_command_names[command_name] = TRUE + continue + + if(command_name_types[command_name]) + if(warnings_only) + SERVER_TOOLS_LOG("WARNING: Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!") + continue + command_name_types[stc] = command_name + + if(!warnings_only) + .[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = initial(stc.required_parameters)) + +/world/proc/HandleServiceCustomCommand(command, sender, params) + var/static/list/cached_custom_server_tools_commands + if(!cached_custom_server_tools_commands) + cached_custom_server_tools_commands = list() + for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command) + var/datum/server_tools_command/stc = I + cached_custom_server_tools_commands[lowertext(initial(stc.name))] = stc + + var/command_type = cached_custom_server_tools_commands[command] + if(!command_type) + return FALSE + var/datum/server_tools_command/stc = new command_type + return stc.Run(sender, params) || TRUE + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/src/DMAPI/tgs/tgs_interface.dm b/src/DMAPI/tgs/tgs_interface.dm new file mode 100644 index 0000000000..931ab10ff3 --- /dev/null +++ b/src/DMAPI/tgs/tgs_interface.dm @@ -0,0 +1,135 @@ +SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(reboot_mode, REBOOT_MODE_NORMAL) +SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(server_tools_api_compatible, FALSE) + +/proc/GetTestMerges() + if(RunningService(TRUE) && fexists(SERVICE_PR_TEST_JSON)) + . = json_decode(file2text(SERVICE_PR_TEST_JSON)) + if(.) + return + return list() + +/world/proc/ServiceInit() + if(!RunningService(TRUE)) + return + ListServiceCustomCommands(TRUE) + ExportService("[SERVICE_REQUEST_API_VERSION] [SERVER_TOOLS_API_VERSION]", TRUE) + +/proc/RunningService(skip_compat_check = FALSE) + if(!skip_compat_check && !SERVER_TOOLS_READ_GLOBAL(server_tools_api_compatible)) + return FALSE + . = world.params[SERVICE_WORLD_PARAM] != null + if(. && world.system_type != MS_WINDOWS) + SERVER_TOOLS_LOG("Warning: Server tools world parameter detected but not running on Windows. Aborting initialization!") + return FALSE + +/proc/ServiceVersion() + if(RunningService(TRUE)) + return world.params[SERVICE_VERSION_PARAM] + +/proc/ServiceAPIVersion() + return SERVICE_API_VERSION_STRING + +/world/proc/ExportService(command, skip_compat_check = FALSE) + . = FALSE + if(!RunningService(skip_compat_check)) + return + if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL)) + CRASH("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.") + var/instance = params[SERVICE_INSTANCE_PARAM] + if(!instance) + instance = "TG Station Server" //maybe just upgraded + call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance, command) //trust no retval + return TRUE + +/world/proc/ChatBroadcast(message) + ExportService("[SERVICE_REQUEST_IRC_BROADCAST] [message]") + +/world/proc/AdminBroadcast(message) + ExportService("[SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE] [message]") + +/world/proc/ServiceEndProcess(silent = FALSE) + SERVER_TOOLS_LOG("Sending shutdown request!"); + sleep(world.tick_lag) //flush the buffers + ExportService(silent ? SERVICE_REQUEST_KILL_PROCESS_SILENT : SERVICE_REQUEST_KILL_PROCESS) + +//called at the exact moment the world is supposed to reboot +/world/proc/ServiceReboot() + switch(SERVER_TOOLS_READ_GLOBAL(reboot_mode)) + if(REBOOT_MODE_HARD) + SERVER_TOOLS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...") + ServiceEndProcess() + if(REBOOT_MODE_SHUTDOWN) + SERVER_TOOLS_WORLD_ANNOUNCE("The server is shutting down...") + ServiceEndProcess() + else + ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know + +/world/proc/ServiceCommand(list/params) + var/their_sCK = params[SERVICE_CMD_PARAM_KEY] + if(!their_sCK || !RunningService(TRUE)) + return FALSE //continue world/Topic + + var/sCK = world.params[SERVICE_WORLD_PARAM] + if(their_sCK != sCK) + return "Invalid comms key!"; + + var/command = params[SERVICE_CMD_PARAM_COMMAND] + if(!command) + return "No command!" + + switch(command) + if(SERVICE_CMD_API_COMPATIBLE) + SERVER_TOOLS_WRITE_GLOBAL(server_tools_api_compatible, TRUE) + return SERVICE_RETURN_SUCCESS + if(SERVICE_CMD_HARD_REBOOT) + if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_HARD) + SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_HARD) + SERVER_TOOLS_LOG("Hard reboot requested by service") + SERVER_TOOLS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by service.") + if(SERVICE_CMD_GRACEFUL_SHUTDOWN) + if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_SHUTDOWN) + SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_SHUTDOWN) + SERVER_TOOLS_LOG("Shutdown requested by service") + message_admins("The world will shutdown at the end of the game. Requested by service.") + if(SERVICE_CMD_WORLD_ANNOUNCE) + var/msg = params["message"] + if(!istext(msg) || !msg) + return "No message set!" + SERVER_TOOLS_WORLD_ANNOUNCE(msg) + return SERVICE_RETURN_SUCCESS + if(SERVICE_CMD_PLAYER_COUNT) + return "[SERVER_TOOLS_CLIENT_COUNT]" + if(SERVICE_CMD_LIST_CUSTOM) + return json_encode(ListServiceCustomCommands(FALSE)) + else + var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM]) + if(custom_command_result) + return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS + return "Unknown command: [command]" + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/tgstation-server.sln b/tgstation-server.sln index 91b6751581..e37c4893b8 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -57,6 +57,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Start EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Service.Tests", "tests\Tgstation.Server.Host.Service.Tests\Tgstation.Server.Host.Service.Tests.csproj", "{AA80A190-52E2-4BE3-BFEB-1F148D9E9007}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DMAPI", "DMAPI", "{7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC}" + ProjectSection(SolutionItems) = preProject + src\DMAPI\tgs.dm = src\DMAPI\tgs.dm + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tgs", "tgs", "{F7765A4B-021C-454E-ABB1-2AB1B85F91B4}" + ProjectSection(SolutionItems) = preProject + src\DMAPI\tgs_commands.dm = src\DMAPI\tgs_commands.dm + src\DMAPI\tgs_interface.dm = src\DMAPI\tgs_interface.dm + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -175,6 +186,9 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {F7765A4B-021C-454E-ABB1-2AB1B85F91B4} = {7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DFD36C95-3E49-41C7-ACDB-86BAF5B18A79} EndGlobalSection From 9a4c9152f842af41623cfe93c2cc36d4fe837b72 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Apr 2018 13:03:21 -0400 Subject: [PATCH 111/397] More API work --- src/DMAPI/tgs.dm | 69 +++++++-- src/DMAPI/tgs/core.dm | 128 +++++++++++++++++ src/DMAPI/tgs/datum.dm | 71 +++++++++ src/DMAPI/tgs/definitions.dm | 2 + src/DMAPI/tgs/tgs_interface.dm | 135 ------------------ .../Components/DreamDaemonParameters.cs | 5 +- tgstation-server.sln | 5 +- 7 files changed, 264 insertions(+), 151 deletions(-) create mode 100644 src/DMAPI/tgs/core.dm create mode 100644 src/DMAPI/tgs/datum.dm create mode 100644 src/DMAPI/tgs/definitions.dm delete mode 100644 src/DMAPI/tgs/tgs_interface.dm diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index d19f2d3a58..8d1a0fe4b7 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,8 +1,8 @@ -// /tg/station 13 server tools DMAPI +//tgstation-server DMAPI //CONFIGURATION -//use this define if you want to do configuration outside of this file +//create this define if you want to do configuration outside of this file #ifndef TGS_EXTERNAL_CONFIGURATION //Comment this out once you've filled in the below @@ -20,34 +20,51 @@ //Set the value in the global variable `Name` to `Value` #define TGS_WRITE_GLOBAL(Name, Value) -//Disallow ANYONE from reflecting a given path, security measure to prevent in-game priveledge escalation +//Disallow ANYONE from reflecting a given `path`, security measure to prevent in-game priveledge escalation #define TGS_PROTECT_DATUM(Path) //display an announcement `message` from the server to all players #define TGS_WORLD_ANNOUNCE(message) -//Write a string `message` to a server log -#define TGS_LOG(message) +//Write an info `message` to a server log +#define TGS_INFO_LOG(message) + +//Write an error `message` to a server log +#define TGS_ERROR_LOG(message) + +//Notify current in-game administrators of a string `event` +#define TGS_NOTIFY_ADMINS(event) //Notify current in-game administrators of a string `event` #define TGS_NOTIFY_ADMINS(event) #endif +//EVENT CODES + +//TODO + //REQUIRED HOOKS //Call this somewhere in /world/New() that is always run -/world/proc/TgsNew() +//event_handler: optional user defined event handler. The default behaviour is to broadcast the event in english to all connected admin channels +/world/proc/TgsNew(datum/tgs_event_handler/event_handler) + return + +//Call this when your initializations are complete and your game is ready to play before any player interactions happen +//This may use world.sleep_offline to make this happen so ensure no changes are made to it while this call is running +/world/proc/TgsInitializationComplete() return //Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified -#define TGS_TOPIC var/tgs_topic_return = TgsCommand(params2list(T)); if(tgs_topic_return) return tgs_topic_return +#define TGS_TOPIC var/tgs_topic_return = TgsTopic(T); if(tgs_topic_return) return tgs_topic_return //Call this at the beginning of world/Reboot(reason) /world/proc/TgsReboot() return //DATUM DEFINITIONS +//unless otherwise specified all datums defined here should be considered read-only, warranty void if written //represents git revision information about the current world build /datum/tgs_revision_information @@ -64,22 +81,38 @@ var/time_merged //timestamp of when the merge commit for the pull request was created var/comment //optional comment left by the one who initiated the test merge -//Gets a list of active `/datum/tgs_revision_information/test_merge`s -/world/proc/TgsGetTestMerges() - return - +//represents a connected chat channel /datum/tgs_chat_channel var/id //internal channel representation var/friendly_name //user friendly channel name var/server_name //server name the channel resides on var/provider_name //chat provider for the channel var/is_admin_channel //if the server operator has marked this channel for game admins only + var/is_private_channel //if this is a private chat channel +//represents a chat user /datum/tgs_chat_user var/id //Internal user representation var/friendly_name //The user's public name - var/datum/tgs_chat_channel //The /datum/tgs_chat_channel this user was from var/mention //The text to use to ping this user in a message + var/datum/tgs_chat_channel //The /datum/tgs_chat_channel this user was from + +//user definable callback for handling events +/datum/tgs_event_handler/proc/HandleEvent() + return + +//user definable chat command +/datum/server_tools_command + var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command + var/help_text = "" //help text for this command + var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins + +//override to implement command +//sender: The tgs_chat_user who send to command +//params: The trimmed string following the command name +//The return value will be stringified and sent to the appropriate chat +/datum/server_tools_command/proc/Run(datum/tgs_chat_user/sender, params) + CRASH("[type] has no implementation for Run()") //FUNCTIONS @@ -99,10 +132,14 @@ /world/proc/TgsVersion() return +//Gets a list of active `/datum/tgs_revision_information/test_merge`s +/world/proc/TgsGetTestMerges() + return + //Forces a hard reboot of BYOND by ending the process //unlike del(world) clients will try to reconnect //If the service has not requested a shutdown, the next server will take over -/world/proc/TgsEndProcess(silent) +/world/proc/TgsEndProcess() return //Gets a list of connected tgs_chat_channel @@ -121,6 +158,12 @@ /world/proc/TgsTargetedChatBroadcast(message, admin_only) return +//Send a private message to a specific user +//message: The message to send +//user: The /datum/tgs_chat_user to send to +/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) + return + /* The MIT License diff --git a/src/DMAPI/tgs/core.dm b/src/DMAPI/tgs/core.dm new file mode 100644 index 0000000000..e2da9fd9a0 --- /dev/null +++ b/src/DMAPI/tgs/core.dm @@ -0,0 +1,128 @@ +/world/TgsNew(datum/tgs_event_handler/event_handler) + var/tgs_version = world.params[TGS_VERSION_PARAMETER] + if(!tgs_version) + return + + var/path = SelectTgsApi(tgs_verison) + 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_api/new_api = new path + TGS_WRITE_GLOBAL(tgs, new_api) + TGS_INFO_LOG("Activated tgstation-server API for version [tgs_version]") + + new_api.OnNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) + +/world/proc/SelectTgsApi(tgs_version) + var/list/version_bits = splittext(tgs_version, ".") + + var/super = text2num(version_bits[0]) + var/major = text2num(version_bits[1]) + var/minor = text2num(version_bits[2]) + var/patch = text2num(version_bits[3]) + + switch(super) + if(3) + switch(major) + if(2) + return /datum/tgs_api/v3201 + + 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/proc/TgsMaximumAPIVersion() + return "4.0.0.0" + +/world/proc/TgsMinimumAPIVersion() + return "3.2.0.0" + +/world/proc/TgsInitializationComplete() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.OnInitializationComplete() + +/world/proc/TgsTopic(T) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.OnTopic(T) + if(result != TGS_UNIMPLEMENTED) + return result + +/world/proc/TgsReboot() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.OnReboot() + +/world/proc/TgsAvailable() + return TGS_READ_GLOBAL(tgs) != null + +/world/proc/TgsVersion() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.TgsVersion() + if(result != TGS_UNIMPLEMENTED) + return result + +/world/proc/TgsGetTestMerges() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.GetTestMerges() + if(result != TGS_UNIMPLEMENTED) + return result + return list() + +/world/proc/TgsEndProcess() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.EndProcess() + +/world/proc/TgsChatChannelInfo() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.ChatChannelInfo() + if(result != TGS_UNIMPLEMENTED) + return result + return list() + +/world/proc/TgsChatBroadcast(message, list/channels) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.ChatBroadcast(message, channels) + +/world/proc/TgsTargetedChatBroadcast(message, admin_only) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.TargetedChatBroadcast(message, admin_only) + +/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.ChatPrivateMessage(message, user) + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/src/DMAPI/tgs/datum.dm b/src/DMAPI/tgs/datum.dm new file mode 100644 index 0000000000..8825082224 --- /dev/null +++ b/src/DMAPI/tgs/datum.dm @@ -0,0 +1,71 @@ +SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(datum/tgs_api/tgs, null) + +/datum/tgs_api + +TGS_PROTECT_DATUM(/datum/tgs_api) + +/datum/tgs_api/proc/ApiVersion() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/OnWorldNew(datum/tgs_event_handler/event_handler) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/OnInitializationComplete() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/OnTopic(T) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/OnReboot() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/TgsAvailable() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/TgsVersion() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/GetTestMerges() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/EndProcess() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/ChatChannelInfo() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/ChatBroadcast(message, list/channels) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/ChatPrivateMessage(message, admin_only) + return TGS_UNIMPLEMENTED + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/src/DMAPI/tgs/definitions.dm b/src/DMAPI/tgs/definitions.dm new file mode 100644 index 0000000000..ebf6d17c2a --- /dev/null +++ b/src/DMAPI/tgs/definitions.dm @@ -0,0 +1,2 @@ +#define TGS_UNIMPLEMENTED "___unimplemented" +#define TGS_VERSION_PARAMETER "server_service_version" diff --git a/src/DMAPI/tgs/tgs_interface.dm b/src/DMAPI/tgs/tgs_interface.dm deleted file mode 100644 index 931ab10ff3..0000000000 --- a/src/DMAPI/tgs/tgs_interface.dm +++ /dev/null @@ -1,135 +0,0 @@ -SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(reboot_mode, REBOOT_MODE_NORMAL) -SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(server_tools_api_compatible, FALSE) - -/proc/GetTestMerges() - if(RunningService(TRUE) && fexists(SERVICE_PR_TEST_JSON)) - . = json_decode(file2text(SERVICE_PR_TEST_JSON)) - if(.) - return - return list() - -/world/proc/ServiceInit() - if(!RunningService(TRUE)) - return - ListServiceCustomCommands(TRUE) - ExportService("[SERVICE_REQUEST_API_VERSION] [SERVER_TOOLS_API_VERSION]", TRUE) - -/proc/RunningService(skip_compat_check = FALSE) - if(!skip_compat_check && !SERVER_TOOLS_READ_GLOBAL(server_tools_api_compatible)) - return FALSE - . = world.params[SERVICE_WORLD_PARAM] != null - if(. && world.system_type != MS_WINDOWS) - SERVER_TOOLS_LOG("Warning: Server tools world parameter detected but not running on Windows. Aborting initialization!") - return FALSE - -/proc/ServiceVersion() - if(RunningService(TRUE)) - return world.params[SERVICE_VERSION_PARAM] - -/proc/ServiceAPIVersion() - return SERVICE_API_VERSION_STRING - -/world/proc/ExportService(command, skip_compat_check = FALSE) - . = FALSE - if(!RunningService(skip_compat_check)) - return - if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL)) - CRASH("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.") - var/instance = params[SERVICE_INSTANCE_PARAM] - if(!instance) - instance = "TG Station Server" //maybe just upgraded - call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance, command) //trust no retval - return TRUE - -/world/proc/ChatBroadcast(message) - ExportService("[SERVICE_REQUEST_IRC_BROADCAST] [message]") - -/world/proc/AdminBroadcast(message) - ExportService("[SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE] [message]") - -/world/proc/ServiceEndProcess(silent = FALSE) - SERVER_TOOLS_LOG("Sending shutdown request!"); - sleep(world.tick_lag) //flush the buffers - ExportService(silent ? SERVICE_REQUEST_KILL_PROCESS_SILENT : SERVICE_REQUEST_KILL_PROCESS) - -//called at the exact moment the world is supposed to reboot -/world/proc/ServiceReboot() - switch(SERVER_TOOLS_READ_GLOBAL(reboot_mode)) - if(REBOOT_MODE_HARD) - SERVER_TOOLS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...") - ServiceEndProcess() - if(REBOOT_MODE_SHUTDOWN) - SERVER_TOOLS_WORLD_ANNOUNCE("The server is shutting down...") - ServiceEndProcess() - else - ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know - -/world/proc/ServiceCommand(list/params) - var/their_sCK = params[SERVICE_CMD_PARAM_KEY] - if(!their_sCK || !RunningService(TRUE)) - return FALSE //continue world/Topic - - var/sCK = world.params[SERVICE_WORLD_PARAM] - if(their_sCK != sCK) - return "Invalid comms key!"; - - var/command = params[SERVICE_CMD_PARAM_COMMAND] - if(!command) - return "No command!" - - switch(command) - if(SERVICE_CMD_API_COMPATIBLE) - SERVER_TOOLS_WRITE_GLOBAL(server_tools_api_compatible, TRUE) - return SERVICE_RETURN_SUCCESS - if(SERVICE_CMD_HARD_REBOOT) - if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_HARD) - SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_HARD) - SERVER_TOOLS_LOG("Hard reboot requested by service") - SERVER_TOOLS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by service.") - if(SERVICE_CMD_GRACEFUL_SHUTDOWN) - if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_SHUTDOWN) - SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_SHUTDOWN) - SERVER_TOOLS_LOG("Shutdown requested by service") - message_admins("The world will shutdown at the end of the game. Requested by service.") - if(SERVICE_CMD_WORLD_ANNOUNCE) - var/msg = params["message"] - if(!istext(msg) || !msg) - return "No message set!" - SERVER_TOOLS_WORLD_ANNOUNCE(msg) - return SERVICE_RETURN_SUCCESS - if(SERVICE_CMD_PLAYER_COUNT) - return "[SERVER_TOOLS_CLIENT_COUNT]" - if(SERVICE_CMD_LIST_CUSTOM) - return json_encode(ListServiceCustomCommands(FALSE)) - else - var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM]) - if(custom_command_result) - return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS - return "Unknown command: [command]" - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs index e6b409730d..e5138d54da 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs @@ -2,8 +2,11 @@ { static class DreamDaemonParameters { + /// + /// Do not change to keep forwards/backwards api compatibility + /// + public const string HostVersion = "server_service_versions"; public const string AccessToken = "tgs_key"; - public const string HostVersion = "tgs_ver"; public const string PrimaryPort = "tgs_port1"; public const string SecondaryPort = "tgs_port2"; public const string IsSecondaryServer = "tgs_second"; diff --git a/tgstation-server.sln b/tgstation-server.sln index e37c4893b8..865182e60a 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -64,8 +64,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DMAPI", "DMAPI", "{7E4E7CF6 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tgs", "tgs", "{F7765A4B-021C-454E-ABB1-2AB1B85F91B4}" ProjectSection(SolutionItems) = preProject - src\DMAPI\tgs_commands.dm = src\DMAPI\tgs_commands.dm - src\DMAPI\tgs_interface.dm = src\DMAPI\tgs_interface.dm + src\DMAPI\tgs\core.dm = src\DMAPI\tgs\core.dm + src\DMAPI\tgs\datum.dm = src\DMAPI\tgs\datum.dm + src\DMAPI\tgs\definitions.dm = src\DMAPI\tgs\definitions.dm EndProjectSection EndProject Global From 89234b900ceade22810926c3d163d75c803207be Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Apr 2018 11:41:46 -0400 Subject: [PATCH 112/397] Finish DMAPI for now Fix build Readd travis --- .travis.yml | 24 ++ build/install_byond.sh | 21 ++ src/DMAPI/tgs.dm | 37 +-- .../{definitions.dm => core/_definitions.dm} | 0 src/DMAPI/tgs/{ => core}/core.dm | 49 ++-- src/DMAPI/tgs/{ => core}/datum.dm | 15 +- src/DMAPI/tgs/includes.dm | 5 + src/DMAPI/tgs/v3210/api.dm | 231 ++++++++++++++++++ .../{tgs_commands.dm => v3210/commands.dm} | 32 +-- .../Rights/DreamDaemonRights.cs | 2 +- .../Components/DreamDaemon.cs | 77 ++++-- .../Components/IDreamDaemon.cs | 4 +- src/Tgstation.Server.Host/Server.cs | 4 +- tests/DMAPI/Config.dm | 10 + tests/DMAPI/Test.dm | 42 ++++ tests/DMAPI/build_byond.sh | 19 ++ tests/DMAPI/travistester.dme | 18 ++ tgstation-server.sln | 37 ++- 18 files changed, 543 insertions(+), 84 deletions(-) create mode 100644 .travis.yml create mode 100755 build/install_byond.sh rename src/DMAPI/tgs/{definitions.dm => core/_definitions.dm} (100%) rename src/DMAPI/tgs/{ => core}/core.dm (78%) rename src/DMAPI/tgs/{ => core}/datum.dm (92%) create mode 100644 src/DMAPI/tgs/includes.dm create mode 100644 src/DMAPI/tgs/v3210/api.dm rename src/DMAPI/tgs/{tgs_commands.dm => v3210/commands.dm} (70%) create mode 100644 tests/DMAPI/Config.dm create mode 100644 tests/DMAPI/Test.dm create mode 100755 tests/DMAPI/build_byond.sh create mode 100644 tests/DMAPI/travistester.dme diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..97c26e9698 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: generic +sudo: false + +env: + global: + - BYOND_MAJOR="511" + - BYOND_MINOR="1385" + - DMEName="tests/DMAPI/travistester.dme" + +cache: + directories: + - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} + +addons: + apt: + packages: + - libc6-i386 + - libstdc++6:i386 + +install: + - build/install_byond.sh + +script: + - test/DMAPI/build_byond.sh diff --git a/build/install_byond.sh b/build/install_byond.sh new file mode 100755 index 0000000000..b2da430247 --- /dev/null +++ b/build/install_byond.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +if [ -d "$HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}/byond/bin" ]; +then + echo "Using cached directory." + exit 0 +else + echo "Setting up BYOND." + mkdir -p "$HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}" + cd "$HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}" + curl "http://www.byond.com/download/build/${BYOND_MAJOR}/${BYOND_MAJOR}.${BYOND_MINOR}_byond_linux.zip" -o byond.zip + unzip byond.zip + cd byond + make here + cd ~/ + exit 0 +fi + +#some variable not set correctly, panic +exit 1 diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 8d1a0fe4b7..9fbaf61555 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,12 +1,14 @@ //tgstation-server DMAPI +//All functions and datums outside this document are subject to change with any version and should not be relied on + //CONFIGURATION //create this define if you want to do configuration outside of this file #ifndef TGS_EXTERNAL_CONFIGURATION //Comment this out once you've filled in the below -#error /tg/station server tools interface unconfigured +#error TGS API unconfigured //Required interfaces (fill in with your codebase equivalent): @@ -26,17 +28,17 @@ //display an announcement `message` from the server to all players #define TGS_WORLD_ANNOUNCE(message) +//Notify current in-game administrators of a string `event` +#define TGS_NOTIFY_ADMINS(event) + //Write an info `message` to a server log #define TGS_INFO_LOG(message) //Write an error `message` to a server log #define TGS_ERROR_LOG(message) -//Notify current in-game administrators of a string `event` -#define TGS_NOTIFY_ADMINS(event) - -//Notify current in-game administrators of a string `event` -#define TGS_NOTIFY_ADMINS(event) +//Get the number of connected /clients +#define TGS_CLIENT_COUNT #endif @@ -92,13 +94,13 @@ //represents a chat user /datum/tgs_chat_user - var/id //Internal user representation - var/friendly_name //The user's public name - var/mention //The text to use to ping this user in a message - var/datum/tgs_chat_channel //The /datum/tgs_chat_channel this user was from + var/id //Internal user representation + var/friendly_name //The user's public name + var/mention //The text to use to ping this user in a message + var/datum/tgs_chat_channel/channel //The /datum/tgs_chat_channel this user was from //user definable callback for handling events -/datum/tgs_event_handler/proc/HandleEvent() +/datum/tgs_event_handler/proc/HandleEvent(event_code) return //user definable chat command @@ -123,17 +125,24 @@ /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 version of the service running the server -/world/proc/TgsVersion() +/world/proc/TgsInstanceName() + return + +//Get the current `/datum/tgs_revision_information` +/world/proc/TgsRevision() return //Gets a list of active `/datum/tgs_revision_information/test_merge`s -/world/proc/TgsGetTestMerges() +/world/proc/TgsTestMerges() return //Forces a hard reboot of BYOND by ending the process diff --git a/src/DMAPI/tgs/definitions.dm b/src/DMAPI/tgs/core/_definitions.dm similarity index 100% rename from src/DMAPI/tgs/definitions.dm rename to src/DMAPI/tgs/core/_definitions.dm diff --git a/src/DMAPI/tgs/core.dm b/src/DMAPI/tgs/core/core.dm similarity index 78% rename from src/DMAPI/tgs/core.dm rename to src/DMAPI/tgs/core/core.dm index e2da9fd9a0..c36caa25c9 100644 --- a/src/DMAPI/tgs/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -8,10 +8,11 @@ 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_api/new_api = new path - TGS_WRITE_GLOBAL(tgs, new_api) TGS_INFO_LOG("Activated tgstation-server API for version [tgs_version]") - - new_api.OnNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) + + var/result = new_api.OnNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) + if(result && result != TGS_UNIMPLEMENTED) + TGS_WRITE_GLOBAL(tgs, new_api) /world/proc/SelectTgsApi(tgs_version) var/list/version_bits = splittext(tgs_version, ".") @@ -25,7 +26,7 @@ if(3) switch(major) if(2) - return /datum/tgs_api/v3201 + return /datum/tgs_api/v3210 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.") @@ -33,51 +34,61 @@ /world/proc/TgsMaximumAPIVersion() return "4.0.0.0" - -/world/proc/TgsMinimumAPIVersion() + +/world/TgsMinimumAPIVersion() return "3.2.0.0" -/world/proc/TgsInitializationComplete() +/world/TgsInitializationComplete() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) api.OnInitializationComplete() -/world/proc/TgsTopic(T) +/world/TgsTopic(T) var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) var/result = api.OnTopic(T) if(result != TGS_UNIMPLEMENTED) return result -/world/proc/TgsReboot() +/world/TgsRevision() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.Revison() + if(result != TGS_UNIMPLEMENTED) + return result + +/world/TgsReboot() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) api.OnReboot() -/world/proc/TgsAvailable() +/world/TgsAvailable() return TGS_READ_GLOBAL(tgs) != null -/world/proc/TgsVersion() +/world/TgsVersion() + return world.params[TGS_VERSION_PARAMETER] + +/world/TgsInstanceName() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) - var/result = api.TgsVersion() + var/result = api.InstanceName() if(result != TGS_UNIMPLEMENTED) return result -/world/proc/TgsGetTestMerges() +/world/TgsTestMerges() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) - var/result = api.GetTestMerges() + var/result = api.TestMerges() if(result != TGS_UNIMPLEMENTED) return result return list() -/world/proc/TgsEndProcess() +/world/TgsEndProcess() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) api.EndProcess() -/world/proc/TgsChatChannelInfo() +/world/TgsChatChannelInfo() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) var/result = api.ChatChannelInfo() @@ -85,17 +96,17 @@ return result return list() -/world/proc/TgsChatBroadcast(message, list/channels) +/world/TgsChatBroadcast(message, list/channels) var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) api.ChatBroadcast(message, channels) -/world/proc/TgsTargetedChatBroadcast(message, admin_only) +/world/TgsTargetedChatBroadcast(message, admin_only) var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) api.TargetedChatBroadcast(message, admin_only) -/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) +/world/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) api.ChatPrivateMessage(message, user) diff --git a/src/DMAPI/tgs/datum.dm b/src/DMAPI/tgs/core/datum.dm similarity index 92% rename from src/DMAPI/tgs/datum.dm rename to src/DMAPI/tgs/core/datum.dm index 8825082224..996412cfeb 100644 --- a/src/DMAPI/tgs/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -2,6 +2,9 @@ SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(datum/tgs_api/tgs, null) /datum/tgs_api +/datum/tgs_api/latest + parent_type = /datum/tgs_api/v3210 + TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/ApiVersion() @@ -19,16 +22,16 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/OnReboot() return TGS_UNIMPLEMENTED -/datum/tgs_api/proc/TgsAvailable() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/TgsVersion() +/datum/tgs_api/proc/InstanceName() return TGS_UNIMPLEMENTED -/datum/tgs_api/proc/GetTestMerges() +/datum/tgs_api/proc/TestMerges() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/EndProcess() return TGS_UNIMPLEMENTED -/datum/tgs_api/proc/EndProcess() +/datum/tgs_api/proc/Revision() return TGS_UNIMPLEMENTED /datum/tgs_api/proc/ChatChannelInfo() diff --git a/src/DMAPI/tgs/includes.dm b/src/DMAPI/tgs/includes.dm new file mode 100644 index 0000000000..1d1d5dce1c --- /dev/null +++ b/src/DMAPI/tgs/includes.dm @@ -0,0 +1,5 @@ +#include "core\_definitions.dm" +#include "core\core.dm" +#include "core\datum.dm" +#include "v3210\api.dm" +#include "v3210\commands.dm" diff --git a/src/DMAPI/tgs/v3210/api.dm b/src/DMAPI/tgs/v3210/api.dm new file mode 100644 index 0000000000..fa495506a0 --- /dev/null +++ b/src/DMAPI/tgs/v3210/api.dm @@ -0,0 +1,231 @@ +#define REBOOT_MODE_NORMAL 0 +#define REBOOT_MODE_HARD 1 +#define REBOOT_MODE_SHUTDOWN 2 + +#define SERVICE_WORLD_PARAM "server_service" +#define SERVICE_INSTANCE_PARAM "server_instance" +#define SERVICE_PR_TEST_JSON "prtestjob.json" +#define SERVICE_INTERFACE_DLL "TGDreamDaemonBridge.dll" +#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint" + +#define SERVICE_CMD_HARD_REBOOT "hard_reboot" +#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown" +#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce" +#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands" +#define SERVICE_CMD_API_COMPATIBLE "api_compat" +#define SERVICE_CMD_PLAYER_COUNT "client_count" + +#define SERVICE_CMD_PARAM_KEY "serviceCommsKey" +#define SERVICE_CMD_PARAM_COMMAND "command" +#define SERVICE_CMD_PARAM_SENDER "sender" +#define SERVICE_CMD_PARAM_CUSTOM "custom" + +#define SERVICE_REQUEST_KILL_PROCESS "killme" +#define SERVICE_REQUEST_IRC_BROADCAST "irc" +#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc" +#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot" +#define SERVICE_REQUEST_API_VERSION "api_ver" + +#define SERVICE_RETURN_SUCCESS "SUCCESS" + +/datum/tgs_api/v3210 + var/reboot_mode = REBOOT_MODE_NORMAL + var/comms_key + var/instance_name + var/originmastercommit + var/commit + var/list/cached_custom_server_tools_commands + var/warned_revison = FALSE + var/warned_custom_commands = FALSE + +/datum/tgs_api/v3210/ApiVersion() + return "3.2.1.0" + +/datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version + comms_key = world.params[SERVICE_WORLD_PARAM] + instance_name = world.params[SERVICE_INSTANCE_PARAM] + if(!instance_name) + instance_name = "TG Station Server" //maybe just upgraded + + + var/list/logs = world.file2list(".git/logs/HEAD") + if(logs) + logs = splittext(logs[logs.len - 1], " ") + date = unix2date(text2num(logs[5])) + commit = logs[2] + log_world("[commit]: [date]") + logs = world.file2list(".git/logs/refs/remotes/origin/master") + if(logs.len) + originmastercommit = splittext(logs[logs.len - 1], " ")[2] + + if(world.system_type != MS_WINDOWS) + TGS_ERROR_LOG("This API version is only supported on Windows. Not running on Windows. Aborting initialization!") + return FALSE + ListServiceCustomCommands(TRUE) + ExportService("[SERVICE_REQUEST_API_VERSION] [SERVER_TOOLS_API_VERSION]", TRUE) + +//nothing to do for v3 +/datum/tgs_api/v3210/OnInitializationComplete() + return + +/datum/tgs_api/v3210/InstanceName() + return world.params[SERVICE_INSTANCE_PARAM] + +/datum/tgs_api/v3210/proc/ExportService(command, skip_compat_check = FALSE) + . = FALSE + if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL)) + TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.") + return + call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance, command) //trust no retval + return TRUE + +/datum/tgs_api/v3210/OnTopic(T) + var/list/params = params2list(T) + var/their_sCK = params[SERVICE_CMD_PARAM_KEY] + if(!their_sCK || !RunningService(TRUE)) + return FALSE //continue world/Topic + + if(their_sCK != comms_key) + return "Invalid comms key!"; + + var/command = params[SERVICE_CMD_PARAM_COMMAND] + if(!command) + return "No command!" + + switch(command) + if(SERVICE_CMD_API_COMPATIBLE) + return SERVICE_RETURN_SUCCESS + if(SERVICE_CMD_HARD_REBOOT) + if(reboot_mode != REBOOT_MODE_HARD) + reboot_mode = REBOOT_MODE_HARD + TGS_INFO_LOG("Hard reboot requested by service") + TGS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by TGS.") + if(SERVICE_CMD_GRACEFUL_SHUTDOWN) + if(reboot_mode != REBOOT_MODE_SHUTDOWN) + reboot_mode = REBOOT_MODE_SHUTDOWN + TGS_INFO_LOG("Shutdown requested by service") + TGS_NOTIFY_ADMINS("The world will shutdown at the end of the game. Requested by TGS.") + if(SERVICE_CMD_WORLD_ANNOUNCE) + var/msg = params["message"] + if(!istext(msg) || !msg) + return "No message set!" + TGS_WORLD_ANNOUNCE(msg) + return SERVICE_RETURN_SUCCESS + if(SERVICE_CMD_PLAYER_COUNT) + return "[TGS_CLIENT_COUNT]" + if(SERVICE_CMD_LIST_CUSTOM) + return json_encode(ListServiceCustomCommands(FALSE)) + else + var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM]) + if(custom_command_result) + return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS + return "Unknown command: [command]" + +/datum/tgs_api/v3210/OnReboot() + switch(reboot_mode) + if(REBOOT_MODE_HARD) + TGS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...") + EndProcess() + if(REBOOT_MODE_SHUTDOWN) + TGS_WORLD_ANNOUNCE("The server is shutting down...") + EndProcess() + else + ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know + +/datum/tgs_api/v3210/TestMerges() + //do the best we can here as the datum can't be completed using the v3 api + . = list + if(!RunningService(TRUE) || !fexists(SERVICE_PR_TEST_JSON)) + return + var/list/json = json_decode(file2text(SERVICE_PR_TEST_JSON)) + if(!json) + return + for(var/I in json) + var/datum/tgs_revision_information/test_merge/tm = new + tm.number = text2num(I) + var/list/entry = json[I] + tm.pull_request_commit = entry["commit"] + tm.author = entry["author"] + tm.title = entry["title"] + . += tm + +/datum/tgs_api/v3210/Revision() + if(!warned_revison) + TGS_ERROR_LOG("Use of TgsRevision on [ApiVersion()] origin_commit only points to master!") + warned_revison = TRUE + var/datum/tgs_revision_information/ri = new + ri.commit = commit + ri.origin_commit = originmastercommit + +/datum/tgs_api/v3210/EndProcess() + sleep(world.tick_lag) //flush the buffers + ExportService(SERVICE_REQUEST_KILL_PROCESS) + +/datum/tgs_api/v3210/ChatChannelInfo() + return list() + +/datum/tgs_api/v3210/ChatBroadcast(message, list/channels) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only) + ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]") + +/datum/tgs_api/v3210/ChatPrivateMessage(message, admin_only) + return TGS_UNIMPLEMENTED + +#undef REBOOT_MODE_NORMAL +#undef REBOOT_MODE_HARD +#undef REBOOT_MODE_SHUTDOWN + +#undef SERVICE_WORLD_PARAM +#undef SERVICE_INSTANCE_PARAM +#undef SERVICE_PR_TEST_JSON +#undef SERVICE_INTERFACE_DLL +#undef SERVICE_INTERFACE_FUNCTION + +#undef SERVICE_CMD_HARD_REBOOT +#undef SERVICE_CMD_GRACEFUL_SHUTDOWN +#undef SERVICE_CMD_WORLD_ANNOUNCE +#undef SERVICE_CMD_LIST_CUSTOM +#undef SERVICE_CMD_API_COMPATIBLE +#undef SERVICE_CMD_PLAYER_COUNT + +#undef SERVICE_CMD_PARAM_KEY +#undef SERVICE_CMD_PARAM_COMMAND +#undef SERVICE_CMD_PARAM_SENDER +#undef SERVICE_CMD_PARAM_CUSTOM + +#undef SERVICE_REQUEST_KILL_PROCESS +#undef SERVICE_REQUEST_IRC_BROADCAST +#undef SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE +#undef SERVICE_REQUEST_WORLD_REBOOT +#undef SERVICE_REQUEST_API_VERSION + +#undef SERVICE_RETURN_SUCCESS "SUCCESS" + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/src/DMAPI/tgs/tgs_commands.dm b/src/DMAPI/tgs/v3210/commands.dm similarity index 70% rename from src/DMAPI/tgs/tgs_commands.dm rename to src/DMAPI/tgs/v3210/commands.dm index ce3fb1bb75..9e4e6979f4 100644 --- a/src/DMAPI/tgs/tgs_commands.dm +++ b/src/DMAPI/tgs/v3210/commands.dm @@ -1,41 +1,36 @@ -/datum/server_tools_command - var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command - var/help_text = "" //help text for this command - var/required_parameters = 0 //number of parameters required for this command - var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins +#define SERVICE_JSON_PARAM_HELPTEXT "help_text" +#define SERVICE_JSON_PARAM_ADMINONLY "admin_only" +#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters" -//override to implement command -//sender is the display name of who sent the command -//params is the trimmed string following the command name -/datum/server_tools_command/proc/Run(sender, params) - CRASH("[type] has no implementation for Run()") - -/world/proc/ListServiceCustomCommands(warnings_only) +/datum/tgs_api/v3210/proc/ListServiceCustomCommands(warnings_only) if(!warnings_only) . = list() var/list/command_name_types = list() var/list/warned_command_names = warnings_only ? list() : null + var/warned_about_the_dangers_of_robutussin = !warnings_only for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command) + if(!warned_about_the_dangers_of_robutussin) + TGS_ERROR_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!") + warned_about_the_dangers_of_robutussin = TRUE var/datum/server_tools_command/stc = I var/command_name = initial(stc.name) var/static/list/warned_server_tools_names = list() if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) if(warnings_only && !warned_command_names[command_name]) - SERVER_TOOLS_LOG("WARNING: Custom command [command_name] can't be used as it is empty or contains illegal characters!") + TGS_ERROR_LOG("Custom command [command_name] can't be used as it is empty or contains illegal characters!") warned_command_names[command_name] = TRUE continue if(command_name_types[command_name]) if(warnings_only) - SERVER_TOOLS_LOG("WARNING: Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!") + TGS_ERROR_LOG("Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!") continue command_name_types[stc] = command_name if(!warnings_only) .[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = initial(stc.required_parameters)) -/world/proc/HandleServiceCustomCommand(command, sender, params) - var/static/list/cached_custom_server_tools_commands +/datum/tgs_api/v3210/proc/HandleServiceCustomCommand(command, sender, params) if(!cached_custom_server_tools_commands) cached_custom_server_tools_commands = list() for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command) @@ -49,6 +44,11 @@ return stc.Run(sender, params) || TRUE /* + +#undef SERVICE_JSON_PARAM_HELPTEXT +#undef SERVICE_JSON_PARAM_ADMINONLY +#undef SERVICE_JSON_PARAM_REQUIREDPARAMETERS + The MIT License Copyright (c) 2017 Jordan Brown diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index 6068b118ed..5fbce5f145 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Rights /// SetSecurity = 8, /// - /// User can read all ports, , , , , and + /// User can read all ports, , , , , and /// ReadMetadata = 16, /// diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index 15926c884d..ab01dba0b7 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components @@ -50,10 +49,6 @@ namespace Tgstation.Server.Host.Components /// readonly IInstanceShutdownMethod instanceShutdownMethod; /// - /// The for - /// - readonly IIOManager ioManager; - /// /// The for /// readonly IDreamDaemonExecutor dreamDaemonExecutor; @@ -63,24 +58,47 @@ namespace Tgstation.Server.Host.Components /// readonly SemaphoreSlim semaphore; + /// + /// If should start DD + /// readonly bool autoStart; + /// + /// The current + /// DreamDaemonLaunchParameters currentLaunchParameters; + /// + /// The for + /// CancellationTokenSource watchdogCancellationTokenSource; + /// + /// The monitor for the DD process + /// Task watchdogTask; - public DreamDaemon(IEventConsumer eventConsumer, IByond byond, ICryptographySuite cryptographySuite, IInterop interop, IInstanceShutdownMethod instanceShutdownMethod, DreamDaemonSettings initialSettings) + /// + /// Construct + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The initial value of and + public DreamDaemon(IEventConsumer eventConsumer, IByond byond, ICryptographySuite cryptographySuite, IInterop interop, IInstanceShutdownMethod instanceShutdownMethod, IDreamDaemonExecutor dreamDaemonExecutor, DreamDaemonSettings initialSettings) { this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); + this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); + currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); interop.SetServerControlHandler(OnServerControl); - currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); autoStart = initialSettings.AutoStart; semaphore = new SemaphoreSlim(1); @@ -94,19 +112,28 @@ namespace Tgstation.Server.Host.Components semaphore.Dispose(); } - Task OnServerControl(ServerControlEventArgs serverControlEventArgs, CancellationToken cancellationToken) + /// + /// Handler for server control events + /// + /// The + /// The for the operation + /// A representing the running operation + async Task OnServerControl(ServerControlEventArgs serverControlEventArgs, CancellationToken cancellationToken) { - lock (this) - { - CancelGracefulActions(); - if (serverControlEventArgs.ProcessRestart) - return Restart(serverControlEventArgs.Graceful, cancellationToken); - if(!serverControlEventArgs.ServerReboot) - return Terminate(serverControlEventArgs.Graceful, cancellationToken); - return Task.CompletedTask; - } + await CancelGracefulActions(cancellationToken).ConfigureAwait(false); + if (serverControlEventArgs.ProcessRestart) + await Restart(serverControlEventArgs.Graceful, cancellationToken).ConfigureAwait(false); + if (!serverControlEventArgs.ServerReboot) + await Terminate(serverControlEventArgs.Graceful, cancellationToken).ConfigureAwait(false); } + /// + /// Main DD execution and monitoring + /// + /// The for the run + /// The to be completed when the server initially starts + /// The for the operation + /// A representing the running operation async Task Watchdog(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) { async Task RunOnce(string executablePath) @@ -146,20 +173,26 @@ namespace Tgstation.Server.Host.Components } /// - public void CancelGracefulActions() + public async Task CancelGracefulActions(CancellationToken cancellationToken) { - lock (this) + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try { SoftRebooting = false; SoftStopping = false; } + finally + { + semaphore.Release(); + } } /// public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { Task launchTask; - lock (this) + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try { currentLaunchParameters = launchParameters; if (!Running) @@ -167,6 +200,10 @@ namespace Tgstation.Server.Host.Components else launchTask = Restart(true, cancellationToken); } + finally + { + semaphore.Release(); + } await launchTask.ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index b49ac50e4c..b052ce9ead 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -76,6 +76,8 @@ namespace Tgstation.Server.Host.Components /// /// Cancels any pending graceful reboots or terminations /// - void CancelGracefulActions(); + /// The for the operation + /// A representing the running operation + Task CancelGracefulActions(CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 6d73783284..506424c49c 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -10,7 +10,7 @@ using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host { /// - sealed class Server : IServer, IServerControl + sealed class Server : IServer, IServerUpdateConsumer { /// public string UpdatePath { get; private set; } @@ -39,7 +39,7 @@ namespace Tgstation.Server.Host using (cancellationTokenSource) using (var webHost = webHostBuilder .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) await webHost.RunAsync(cancellationToken).ConfigureAwait(false); diff --git a/tests/DMAPI/Config.dm b/tests/DMAPI/Config.dm new file mode 100644 index 0000000000..6c7a2564ef --- /dev/null +++ b/tests/DMAPI/Config.dm @@ -0,0 +1,10 @@ +#define TGS_EXTERNAL_CONFIGURATION +#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) var/##Name = ##Value +#define TGS_READ_GLOBAL(Name) global.##Name +#define TGS_WRITE_GLOBAL(Name, Value) global.##Name = ##Value +#define TGS_PROTECT_DATUM(Path) +#define TGS_WORLD_ANNOUNCE(message) world << ##message +#define TGS_INFO_LOG(message) world.log << "Info: [##message]" +#define TGS_ERROR_LOG(message) world.log << "Error: [##message]" +#define TGS_NOTIFY_ADMINS(event) message_admins(event) +#define TGS_CLIENT_COUNT clients.len diff --git a/tests/DMAPI/Test.dm b/tests/DMAPI/Test.dm new file mode 100644 index 0000000000..91c97a26d6 --- /dev/null +++ b/tests/DMAPI/Test.dm @@ -0,0 +1,42 @@ +/world/New() + SERVER_TOOLS_ON_NEW + world.log << "Service API Version: [SERVER_TOOLS_API_VERSION]" + WaitForAPICompatResponse() + +/world/Topic(T, Addr, Master, Keys) + SERVER_TOOLS_ON_TOPIC + +/world/Reboot(reason) + SERVER_TOOLS_ON_REBOOT + SERVER_TOOLS_REBOOT_BYOND(FALSE) + +/proc/message_admins(event) + world << event + world.log << event + +/proc/WaitForAPICompatResponse() + set waitfor = FALSE + sleep(50) + if(!SERVER_TOOLS_PRESENT) + world.log << "No running service detected" + del(world) + return + world.log << "Running service version: [SERVER_TOOLS_VERSION]" + var/list/prs = SERVER_TOOLS_PR_LIST + for(var/I in prs) + var/data = prs[I] + world.log << "Testmerge: #[I]: [data["title"]] by [data["author"]] at commit [data["commit"]]" + var/checks_complete = "Server tools API checks complete! Rebooting..." + SERVER_TOOLS_CHAT_BROADCAST(checks_complete) + SERVER_TOOLS_RELAY_BROADCAST(checks_complete) + world.Reboot() + +var/list/clients = list() + +/client/New() + clients += src + return ..() + +/client/Del() + clients -= src + return ..() diff --git a/tests/DMAPI/build_byond.sh b/tests/DMAPI/build_byond.sh new file mode 100755 index 0000000000..ac4473ad9a --- /dev/null +++ b/tests/DMAPI/build_byond.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e +retval=1 +source $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}/byond/bin/byondsetup + +if hash DreamMaker 2>/dev/null +then + DreamMaker $DMEName 2>&1 | tee result.log + retval=$? + if ! grep '\- 0 errors, 0 warnings' result.log + then + retval=1 #hard fail, due to warnings or errors + fi +else + echo "Couldn't find the DreamMaker executable, aborting." + retval=2 +fi +exit $retval \ No newline at end of file diff --git a/tests/DMAPI/travistester.dme b/tests/DMAPI/travistester.dme new file mode 100644 index 0000000000..c3b64fc12b --- /dev/null +++ b/tests/DMAPI/travistester.dme @@ -0,0 +1,18 @@ +// Hand crafted DME, will not work if saved with DreamMaker + +// BEGIN_INTERNALS +// END_INTERNALS + +// BEGIN_FILE_DIR +#define FILE_DIR . +// END_FILE_DIR + +// BEGIN_PREFERENCES +// END_PREFERENCES + +// BEGIN_INCLUDE +#include "Config.dm" +#include "..\..\src\DMAPI\tgs.dm" +#include "..\..\src\DMAPI\tgs\includes.dm" +#include "Test.dm" +// END_INCLUDE diff --git a/tgstation-server.sln b/tgstation-server.sln index 865182e60a..a899fc5709 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .codecov.yml = .codecov.yml .gitignore = .gitignore + .travis.yml = .travis.yml appveyor.yml = appveyor.yml LICENSE = LICENSE README.md = README.md @@ -21,6 +22,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6 build\BuildDox.ps1 = build\BuildDox.ps1 build\Dockerfile = build\Dockerfile build\Doxyfile = build\Doxyfile + build\install_byond.sh = build\install_byond.sh build\tgs.ico = build\tgs.ico build\UploadCoverage.ps1 = build\UploadCoverage.ps1 EndProjectSection @@ -58,15 +60,36 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Service.Tests", "tests\Tgstation.Server.Host.Service.Tests\Tgstation.Server.Host.Service.Tests.csproj", "{AA80A190-52E2-4BE3-BFEB-1F148D9E9007}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DMAPI", "DMAPI", "{7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tgs", "tgs", "{F7765A4B-021C-454E-ABB1-2AB1B85F91B4}" + ProjectSection(SolutionItems) = preProject + src\DMAPI\tgs\includes.dm = src\DMAPI\tgs\includes.dm + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{DCCBA9DA-47BA-4C70-823B-E99A3ACA0377}" + ProjectSection(SolutionItems) = preProject + src\DMAPI\tgs\core\_definitions.dm = src\DMAPI\tgs\core\_definitions.dm + src\DMAPI\tgs\core\core.dm = src\DMAPI\tgs\core\core.dm + src\DMAPI\tgs\core\datum.dm = src\DMAPI\tgs\core\datum.dm + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v3210", "v3210", "{1B228ACB-60C3-4DF9-B716-9F0BD31F6766}" + ProjectSection(SolutionItems) = preProject + src\DMAPI\tgs\v3210\api.dm = src\DMAPI\tgs\v3210\api.dm + src\DMAPI\tgs\v3210\commands.dm = src\DMAPI\tgs\v3210\commands.dm + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3BB10856-AA01-43D1-AAB0-A013085426D6}" ProjectSection(SolutionItems) = preProject src\DMAPI\tgs.dm = src\DMAPI\tgs.dm EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tgs", "tgs", "{F7765A4B-021C-454E-ABB1-2AB1B85F91B4}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{82066812-6C73-4360-943B-B23F2F491261}" ProjectSection(SolutionItems) = preProject - src\DMAPI\tgs\core.dm = src\DMAPI\tgs\core.dm - src\DMAPI\tgs\datum.dm = src\DMAPI\tgs\datum.dm - src\DMAPI\tgs\definitions.dm = src\DMAPI\tgs\definitions.dm + tests\DMAPI\Config.dm = tests\DMAPI\Config.dm + tests\DMAPI\install_byond.sh = tests\DMAPI\install_byond.sh + tests\DMAPI\Test.dm = tests\DMAPI\Test.dm + tests\DMAPI\travistester.dme = tests\DMAPI\travistester.dme EndProjectSection EndProject Global @@ -188,7 +211,11 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {F7765A4B-021C-454E-ABB1-2AB1B85F91B4} = {7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC} + {F7765A4B-021C-454E-ABB1-2AB1B85F91B4} = {3BB10856-AA01-43D1-AAB0-A013085426D6} + {DCCBA9DA-47BA-4C70-823B-E99A3ACA0377} = {F7765A4B-021C-454E-ABB1-2AB1B85F91B4} + {1B228ACB-60C3-4DF9-B716-9F0BD31F6766} = {F7765A4B-021C-454E-ABB1-2AB1B85F91B4} + {3BB10856-AA01-43D1-AAB0-A013085426D6} = {7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC} + {82066812-6C73-4360-943B-B23F2F491261} = {7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DFD36C95-3E49-41C7-ACDB-86BAF5B18A79} From 66d43823443b8cdb08a0f850930a73d89de92e88 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Apr 2018 12:06:16 -0400 Subject: [PATCH 113/397] Fix DMAPI test build --- .gitignore | 3 + .travis.yml | 5 +- src/DMAPI/tgs.dm | 5 +- src/DMAPI/tgs/core/core.dm | 50 +++++++------- src/DMAPI/tgs/core/datum.dm | 46 ++++++------- src/DMAPI/tgs/core/default_event_handler.dm | 30 +++++++++ src/DMAPI/tgs/includes.dm | 1 + src/DMAPI/tgs/v3210/api.dm | 75 ++++++++++++--------- src/DMAPI/tgs/v3210/commands.dm | 21 +++--- tests/DMAPI/Test.dm | 28 ++------ tgstation-server.sln | 1 + 11 files changed, 147 insertions(+), 118 deletions(-) create mode 100644 src/DMAPI/tgs/core/default_event_handler.dm diff --git a/.gitignore b/.gitignore index 9b60069c91..5074f2b86e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ artifacts/ *.sln.ide /TestResults /src/Tgstation.Server.Host/appsettings.Development.json +/tests/DMAPI/travistester.lk +/tests/DMAPI/travistester.int +/tests/DMAPI/travistester.dmb diff --git a/.travis.yml b/.travis.yml index 97c26e9698..3643b77270 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: generic sudo: false - +git: + depth: 1 env: global: - BYOND_MAJOR="511" @@ -21,4 +22,4 @@ install: - build/install_byond.sh script: - - test/DMAPI/build_byond.sh + - tests/DMAPI/build_byond.sh diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 9fbaf61555..e885b06397 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -76,6 +76,7 @@ //represents a merge of a GitHub pull request /datum/tgs_revision_information/test_merge var/number //pull request number + var/title //pull request title var/body //pull request body var/author //pull request github author var/url //link to pull request html @@ -104,7 +105,7 @@ return //user definable chat command -/datum/server_tools_command +/datum/tgs_chat_command var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command var/help_text = "" //help text for this command var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins @@ -113,7 +114,7 @@ //sender: The tgs_chat_user who send to command //params: The trimmed string following the command name //The return value will be stringified and sent to the appropriate chat -/datum/server_tools_command/proc/Run(datum/tgs_chat_user/sender, params) +/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params) CRASH("[type] has no implementation for Run()") //FUNCTIONS diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index c36caa25c9..ffeab4289b 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -3,20 +3,20 @@ if(!tgs_version) return - var/path = SelectTgsApi(tgs_verison) + 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_api/new_api = new path TGS_INFO_LOG("Activated tgstation-server API for version [tgs_version]") - - var/result = new_api.OnNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) + + var/result = new_api.OnWorldNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) if(result && result != TGS_UNIMPLEMENTED) TGS_WRITE_GLOBAL(tgs, new_api) /world/proc/SelectTgsApi(tgs_version) var/list/version_bits = splittext(tgs_version, ".") - + var/super = text2num(version_bits[0]) var/major = text2num(version_bits[1]) var/minor = text2num(version_bits[2]) @@ -27,14 +27,14 @@ switch(major) if(2) return /datum/tgs_api/v3210 - + 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/proc/TgsMaximumAPIVersion() +/world/TgsMaximumAPIVersion() return "4.0.0.0" - + /world/TgsMinimumAPIVersion() return "3.2.0.0" @@ -43,7 +43,7 @@ if(api) api.OnInitializationComplete() -/world/TgsTopic(T) +/world/proc/TgsTopic(T) var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) var/result = api.OnTopic(T) @@ -53,7 +53,7 @@ /world/TgsRevision() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) - var/result = api.Revison() + var/result = api.Revision() if(result != TGS_UNIMPLEMENTED) return result @@ -104,7 +104,7 @@ /world/TgsTargetedChatBroadcast(message, admin_only) var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) - api.TargetedChatBroadcast(message, admin_only) + api.ChatTargetedBroadcast(message, admin_only) /world/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) @@ -116,24 +116,24 @@ The MIT License Copyright (c) 2017 Jordan Brown -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index 996412cfeb..0a8782529e 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -1,4 +1,4 @@ -SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(datum/tgs_api/tgs, null) +TGS_DEFINE_AND_SET_GLOBAL(datum/tgs_api/tgs, null) /datum/tgs_api @@ -9,25 +9,25 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/ApiVersion() return TGS_UNIMPLEMENTED - + /datum/tgs_api/proc/OnWorldNew(datum/tgs_event_handler/event_handler) - return TGS_UNIMPLEMENTED + return TGS_UNIMPLEMENTED /datum/tgs_api/proc/OnInitializationComplete() return TGS_UNIMPLEMENTED /datum/tgs_api/proc/OnTopic(T) return TGS_UNIMPLEMENTED - + /datum/tgs_api/proc/OnReboot() return TGS_UNIMPLEMENTED /datum/tgs_api/proc/InstanceName() return TGS_UNIMPLEMENTED - + /datum/tgs_api/proc/TestMerges() return TGS_UNIMPLEMENTED - + /datum/tgs_api/proc/EndProcess() return TGS_UNIMPLEMENTED @@ -36,10 +36,10 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/ChatChannelInfo() return TGS_UNIMPLEMENTED - + /datum/tgs_api/proc/ChatBroadcast(message, list/channels) return TGS_UNIMPLEMENTED - + /datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only) return TGS_UNIMPLEMENTED @@ -51,24 +51,24 @@ The MIT License Copyright (c) 2017 Jordan Brown -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ diff --git a/src/DMAPI/tgs/core/default_event_handler.dm b/src/DMAPI/tgs/core/default_event_handler.dm new file mode 100644 index 0000000000..716715bb26 --- /dev/null +++ b/src/DMAPI/tgs/core/default_event_handler.dm @@ -0,0 +1,30 @@ +/datum/tgs_event_handler/tgs_default/HandleEvent(event_code) + //TODO + return + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/src/DMAPI/tgs/includes.dm b/src/DMAPI/tgs/includes.dm index 1d1d5dce1c..7ca906c840 100644 --- a/src/DMAPI/tgs/includes.dm +++ b/src/DMAPI/tgs/includes.dm @@ -1,5 +1,6 @@ #include "core\_definitions.dm" #include "core\core.dm" #include "core\datum.dm" +#include "core\default_event_handler.dm" #include "v3210\api.dm" #include "v3210\commands.dm" diff --git a/src/DMAPI/tgs/v3210/api.dm b/src/DMAPI/tgs/v3210/api.dm index fa495506a0..7de257f9b4 100644 --- a/src/DMAPI/tgs/v3210/api.dm +++ b/src/DMAPI/tgs/v3210/api.dm @@ -34,40 +34,51 @@ var/instance_name var/originmastercommit var/commit - var/list/cached_custom_server_tools_commands + var/list/cached_custom_tgs_chat_commands var/warned_revison = FALSE var/warned_custom_commands = FALSE /datum/tgs_api/v3210/ApiVersion() return "3.2.1.0" - + +/datum/tgs_api/v3210/proc/trim_left(text) + for (var/i = 1 to length(text)) + if (text2ascii(text, i) > 32) + return copytext(text, i) + return "" + +/datum/tgs_api/v3210/proc/trim_right(text) + for (var/i = length(text), i > 0, i--) + if (text2ascii(text, i) > 32) + return copytext(text, 1, i + 1) + return "" + /datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version comms_key = world.params[SERVICE_WORLD_PARAM] instance_name = world.params[SERVICE_INSTANCE_PARAM] if(!instance_name) instance_name = "TG Station Server" //maybe just upgraded - - var/list/logs = world.file2list(".git/logs/HEAD") - if(logs) + var/list/logs = splittext(trim_left(trim_right(file2text(".git/logs/HEAD"))), "\n") + if(logs.len) logs = splittext(logs[logs.len - 1], " ") - date = unix2date(text2num(logs[5])) commit = logs[2] - log_world("[commit]: [date]") logs = world.file2list(".git/logs/refs/remotes/origin/master") if(logs.len) - originmastercommit = splittext(logs[logs.len - 1], " ")[2] + originmastercommit = splittext(logs[logs.len - 1], " ")[2] if(world.system_type != MS_WINDOWS) TGS_ERROR_LOG("This API version is only supported on Windows. Not running on Windows. Aborting initialization!") return FALSE ListServiceCustomCommands(TRUE) - ExportService("[SERVICE_REQUEST_API_VERSION] [SERVER_TOOLS_API_VERSION]", TRUE) + ExportService("[SERVICE_REQUEST_API_VERSION] [ApiVersion()]", TRUE) + +/world/proc/file2list(filename, seperator="\n", trim = TRUE) //nothing to do for v3 /datum/tgs_api/v3210/OnInitializationComplete() return - + /datum/tgs_api/v3210/InstanceName() return world.params[SERVICE_INSTANCE_PARAM] @@ -76,13 +87,13 @@ if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL)) TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.") return - call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance, command) //trust no retval + call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval return TRUE /datum/tgs_api/v3210/OnTopic(T) var/list/params = params2list(T) var/their_sCK = params[SERVICE_CMD_PARAM_KEY] - if(!their_sCK || !RunningService(TRUE)) + if(!their_sCK) return FALSE //continue world/Topic if(their_sCK != comms_key) @@ -120,7 +131,7 @@ if(custom_command_result) return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS return "Unknown command: [command]" - + /datum/tgs_api/v3210/OnReboot() switch(reboot_mode) if(REBOOT_MODE_HARD) @@ -131,11 +142,11 @@ EndProcess() else ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know - + /datum/tgs_api/v3210/TestMerges() //do the best we can here as the datum can't be completed using the v3 api - . = list - if(!RunningService(TRUE) || !fexists(SERVICE_PR_TEST_JSON)) + . = list() + if(!fexists(SERVICE_PR_TEST_JSON)) return var/list/json = json_decode(file2text(SERVICE_PR_TEST_JSON)) if(!json) @@ -201,31 +212,31 @@ #undef SERVICE_REQUEST_WORLD_REBOOT #undef SERVICE_REQUEST_API_VERSION -#undef SERVICE_RETURN_SUCCESS "SUCCESS" +#undef SERVICE_RETURN_SUCCESS /* The MIT License Copyright (c) 2017 Jordan Brown -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ diff --git a/src/DMAPI/tgs/v3210/commands.dm b/src/DMAPI/tgs/v3210/commands.dm index 9e4e6979f4..e823c16d26 100644 --- a/src/DMAPI/tgs/v3210/commands.dm +++ b/src/DMAPI/tgs/v3210/commands.dm @@ -8,13 +8,12 @@ var/list/command_name_types = list() var/list/warned_command_names = warnings_only ? list() : null var/warned_about_the_dangers_of_robutussin = !warnings_only - for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command) + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) if(!warned_about_the_dangers_of_robutussin) TGS_ERROR_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!") warned_about_the_dangers_of_robutussin = TRUE - var/datum/server_tools_command/stc = I + var/datum/tgs_chat_command/stc = I var/command_name = initial(stc.name) - var/static/list/warned_server_tools_names = list() if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) if(warnings_only && !warned_command_names[command_name]) TGS_ERROR_LOG("Custom command [command_name] can't be used as it is empty or contains illegal characters!") @@ -28,19 +27,19 @@ command_name_types[stc] = command_name if(!warnings_only) - .[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = initial(stc.required_parameters)) + .[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = 0) /datum/tgs_api/v3210/proc/HandleServiceCustomCommand(command, sender, params) - if(!cached_custom_server_tools_commands) - cached_custom_server_tools_commands = list() - for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command) - var/datum/server_tools_command/stc = I - cached_custom_server_tools_commands[lowertext(initial(stc.name))] = stc + if(!cached_custom_tgs_chat_commands) + cached_custom_tgs_chat_commands = list() + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) + var/datum/tgs_chat_command/stc = I + cached_custom_tgs_chat_commands[lowertext(initial(stc.name))] = stc - var/command_type = cached_custom_server_tools_commands[command] + var/command_type = cached_custom_tgs_chat_commands[command] if(!command_type) return FALSE - var/datum/server_tools_command/stc = new command_type + var/datum/tgs_chat_command/stc = new command_type return stc.Run(sender, params) || TRUE /* diff --git a/tests/DMAPI/Test.dm b/tests/DMAPI/Test.dm index 91c97a26d6..addf8c7603 100644 --- a/tests/DMAPI/Test.dm +++ b/tests/DMAPI/Test.dm @@ -1,36 +1,18 @@ /world/New() - SERVER_TOOLS_ON_NEW - world.log << "Service API Version: [SERVER_TOOLS_API_VERSION]" - WaitForAPICompatResponse() + TgsNew() + TgsInitializationComplete() /world/Topic(T, Addr, Master, Keys) - SERVER_TOOLS_ON_TOPIC + TGS_TOPIC /world/Reboot(reason) - SERVER_TOOLS_ON_REBOOT - SERVER_TOOLS_REBOOT_BYOND(FALSE) + TgsReboot() /proc/message_admins(event) + event = "Admins: [event]" world << event world.log << event -/proc/WaitForAPICompatResponse() - set waitfor = FALSE - sleep(50) - if(!SERVER_TOOLS_PRESENT) - world.log << "No running service detected" - del(world) - return - world.log << "Running service version: [SERVER_TOOLS_VERSION]" - var/list/prs = SERVER_TOOLS_PR_LIST - for(var/I in prs) - var/data = prs[I] - world.log << "Testmerge: #[I]: [data["title"]] by [data["author"]] at commit [data["commit"]]" - var/checks_complete = "Server tools API checks complete! Rebooting..." - SERVER_TOOLS_CHAT_BROADCAST(checks_complete) - SERVER_TOOLS_RELAY_BROADCAST(checks_complete) - world.Reboot() - var/list/clients = list() /client/New() diff --git a/tgstation-server.sln b/tgstation-server.sln index a899fc5709..3cd8efcb76 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -71,6 +71,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{DCCBA9DA-4 src\DMAPI\tgs\core\_definitions.dm = src\DMAPI\tgs\core\_definitions.dm src\DMAPI\tgs\core\core.dm = src\DMAPI\tgs\core\core.dm src\DMAPI\tgs\core\datum.dm = src\DMAPI\tgs\core\datum.dm + src\DMAPI\tgs\core\default_event_handler.dm = src\DMAPI\tgs\core\default_event_handler.dm EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v3210", "v3210", "{1B228ACB-60C3-4DF9-B716-9F0BD31F6766}" From 492d3593c4f097f42f454b42502f1200279b9877 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 25 Apr 2018 12:51:01 -0400 Subject: [PATCH 114/397] Removes randomass file2list declaration --- src/DMAPI/tgs/v3210/api.dm | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/DMAPI/tgs/v3210/api.dm b/src/DMAPI/tgs/v3210/api.dm index 7de257f9b4..8823aa6da0 100644 --- a/src/DMAPI/tgs/v3210/api.dm +++ b/src/DMAPI/tgs/v3210/api.dm @@ -73,8 +73,6 @@ ListServiceCustomCommands(TRUE) ExportService("[SERVICE_REQUEST_API_VERSION] [ApiVersion()]", TRUE) -/world/proc/file2list(filename, seperator="\n", trim = TRUE) - //nothing to do for v3 /datum/tgs_api/v3210/OnInitializationComplete() return From ba9dcad8b56ec439474a1e22cb4a3d26b6ebd867 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 25 Apr 2018 12:54:03 -0400 Subject: [PATCH 115/397] AHHHHH --- src/DMAPI/tgs/v3210/api.dm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/DMAPI/tgs/v3210/api.dm b/src/DMAPI/tgs/v3210/api.dm index 8823aa6da0..38e264df98 100644 --- a/src/DMAPI/tgs/v3210/api.dm +++ b/src/DMAPI/tgs/v3210/api.dm @@ -53,17 +53,20 @@ return copytext(text, 1, i + 1) return "" +/datum/tgs_api/v3210/proc/file2list(filename) + return splittext(trim_left(trim_right(file2text(filename))), "\n") + /datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version comms_key = world.params[SERVICE_WORLD_PARAM] instance_name = world.params[SERVICE_INSTANCE_PARAM] if(!instance_name) instance_name = "TG Station Server" //maybe just upgraded - var/list/logs = splittext(trim_left(trim_right(file2text(".git/logs/HEAD"))), "\n") + var/list/logs = sfile2list(".git/logs/HEAD") if(logs.len) logs = splittext(logs[logs.len - 1], " ") commit = logs[2] - logs = world.file2list(".git/logs/refs/remotes/origin/master") + logs = file2list(".git/logs/refs/remotes/origin/master") if(logs.len) originmastercommit = splittext(logs[logs.len - 1], " ")[2] From 2084f4117eaeeffce90410d4e59c7a452f1f6220 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 25 Apr 2018 12:59:35 -0400 Subject: [PATCH 116/397] Update api.dm --- src/DMAPI/tgs/v3210/api.dm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/DMAPI/tgs/v3210/api.dm b/src/DMAPI/tgs/v3210/api.dm index 38e264df98..5f69889f07 100644 --- a/src/DMAPI/tgs/v3210/api.dm +++ b/src/DMAPI/tgs/v3210/api.dm @@ -62,7 +62,7 @@ if(!instance_name) instance_name = "TG Station Server" //maybe just upgraded - var/list/logs = sfile2list(".git/logs/HEAD") + var/list/logs = file2list(".git/logs/HEAD") if(logs.len) logs = splittext(logs[logs.len - 1], " ") commit = logs[2] @@ -177,7 +177,10 @@ return list() /datum/tgs_api/v3210/ChatBroadcast(message, list/channels) - return TGS_UNIMPLEMENTED + if(channels) + return TGS_UNIMPLEMENTED + ChatTargetedBroadcast(message, TRUE) + ChatTargetedBroadcast(message, FALSE) /datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only) ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]") From fea027586ea34007bdd8a1a3e76d7268a5d37d16 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 25 Apr 2018 13:11:51 -0400 Subject: [PATCH 117/397] Fix global declaration --- src/DMAPI/tgs/core/datum.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index 0a8782529e..6af39a75df 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -1,4 +1,4 @@ -TGS_DEFINE_AND_SET_GLOBAL(datum/tgs_api/tgs, null) +TGS_DEFINE_AND_SET_GLOBAL(tgs, null) /datum/tgs_api From c27b56c7cc89491b57d954e50a92445a8607e350 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Apr 2018 16:58:04 -0400 Subject: [PATCH 118/397] Work on the new watchdog --- .../Components/DreamDaemon.cs | 186 ++++++++++++++---- .../Components/DreamDaemonExecutor.cs | 8 +- .../Components/EventType.cs | 8 + .../Components/IByond.cs | 5 +- .../Components/IDmbFactory.cs | 18 ++ .../Components/IDmbProvider.cs | 25 +++ .../Components/IInterop.cs | 6 +- .../Components/ServerControlEvent.cs | 20 ++ .../Components/ServerControlEventArgs.cs | 27 --- .../Components/ServerControlEventType.cs | 21 ++ 10 files changed, 249 insertions(+), 75 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IDmbFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/IDmbProvider.cs create mode 100644 src/Tgstation.Server.Host/Components/ServerControlEvent.cs delete mode 100644 src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs create mode 100644 src/Tgstation.Server.Host/Components/ServerControlEventType.cs diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index ab01dba0b7..329a84418e 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -52,6 +52,10 @@ namespace Tgstation.Server.Host.Components /// The for /// readonly IDreamDaemonExecutor dreamDaemonExecutor; + /// + /// The for + /// + readonly IDmbFactory dmbFactory; /// /// Used for write control to class variables @@ -76,6 +80,18 @@ namespace Tgstation.Server.Host.Components /// The monitor for the DD process /// Task watchdogTask; + /// + /// to complete when the primary server is primed + /// + TaskCompletionSource onPrimaryServerPrimed; + /// + /// to complete when the primary server is rebooted + /// + TaskCompletionSource onPrimaryServerRebooted; + /// + /// to complete when the secondary server is rebooted + /// + TaskCompletionSource onSecondaryServerRebooted; /// /// Construct @@ -86,8 +102,9 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The value of /// The initial value of and - public DreamDaemon(IEventConsumer eventConsumer, IByond byond, ICryptographySuite cryptographySuite, IInterop interop, IInstanceShutdownMethod instanceShutdownMethod, IDreamDaemonExecutor dreamDaemonExecutor, DreamDaemonSettings initialSettings) + public DreamDaemon(IEventConsumer eventConsumer, IByond byond, ICryptographySuite cryptographySuite, IInterop interop, IInstanceShutdownMethod instanceShutdownMethod, IDreamDaemonExecutor dreamDaemonExecutor, IDmbFactory dmbFactory, DreamDaemonSettings initialSettings) { this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); @@ -95,6 +112,7 @@ namespace Tgstation.Server.Host.Components this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); + this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); interop.SetServerControlHandler(OnServerControl); @@ -115,61 +133,147 @@ namespace Tgstation.Server.Host.Components /// /// Handler for server control events /// - /// The + /// The /// The for the operation /// A representing the running operation - async Task OnServerControl(ServerControlEventArgs serverControlEventArgs, CancellationToken cancellationToken) + static async Task OnServerControl(ServerControlEvent serverControlEventArgs, CancellationToken cancellationToken) { - await CancelGracefulActions(cancellationToken).ConfigureAwait(false); - if (serverControlEventArgs.ProcessRestart) - await Restart(serverControlEventArgs.Graceful, cancellationToken).ConfigureAwait(false); - if (!serverControlEventArgs.ServerReboot) - await Terminate(serverControlEventArgs.Graceful, cancellationToken).ConfigureAwait(false); + if (serverControlEventArgs == null) + throw new ArgumentNullException(nameof(serverControlEventArgs)); + + await Task.Yield(); + throw new NotImplementedException(); } - + /// /// Main DD execution and monitoring /// - /// The for the run /// The to be completed when the server initially starts /// The for the operation /// A representing the running operation - async Task Watchdog(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) + async Task Watchdog(TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) { - async Task RunOnce(string executablePath) + if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null) + throw new InvalidOperationException("No byond version installed!"); + await byond.ClearCache(cancellationToken).ConfigureAwait(false); + + //lock the byond executable and run the server + async Task RunServer(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, string accessToken, bool isPrimary, CancellationToken serverCancellationToken) { - if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null) - throw new InvalidOperationException("No byond version installed!"); + using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) + { + return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, String.Concat(dmb.PrimaryDirectory, dmb.DmbName), accessToken, isPrimary, serverCancellationToken).ConfigureAwait(false); + } + }; - await byond.ClearCache(cancellationToken).ConfigureAwait(false); - - string dmb = null; //TODO - var accessToken = cryptographySuite.GetSecureString(); - var usePrimaryPort = true; - - var ddTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, executablePath, dmb, accessToken, usePrimaryPort, cancellationToken); - - await onSuccessfulStartup.Task.ConfigureAwait(false); - - interop.SetRun(usePrimaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken); - - int ddExitCode; + void StartServer(DreamDaemonLaunchParameters launchParameters, bool isPrimary, out Task ddTask, out CancellationTokenSource cancellationTokenSource) + { + cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try { - ddExitCode = await ddTask.ConfigureAwait(false); + var accessToken = cryptographySuite.GetSecureString(); + var ddToken = cancellationTokenSource.Token; + ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, dreamDaemonPath, accessToken, isPrimary, ddToken), false, true); + interop.SetRun(isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken, isPrimary); + } + catch + { + cancellationTokenSource.Dispose(); + throw; + } + }; + + bool secondaryIsOther = true; + var retries = 0; + do + { + var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour + await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); + + //load the event tcs' and get the initial launch parameters + var primaryRebootedTcs = new TaskCompletionSource(); + var secondaryRebootedTcs = new TaskCompletionSource(); + var primaryPrimedTcs = new TaskCompletionSource(); + DreamDaemonLaunchParameters initialLaunchParameters; + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + onPrimaryServerRebooted = primaryRebootedTcs; + onSecondaryServerRebooted = secondaryRebootedTcs; + onPrimaryServerPrimed = primaryPrimedTcs; + initialLaunchParameters = currentLaunchParameters; } finally { - interop.SetRun(null, null); + semaphore.Release(); } - await eventConsumer.HandleEvent(ddExitCode != 0 ? EventType.DDCrash : EventType.DDExit, null, cancellationToken).ConfigureAwait(false); - }; + //start the primary server + StartServer(initialLaunchParameters, true, out Task ddPrimaryTask, out CancellationTokenSource primaryCts); + using (primaryCts) + { + //wait to make sure we got this far + await onSuccessfulStartup.Task.ConfigureAwait(false); + onSuccessfulStartup = null; - do - { - await byond.UseExecutable(RunOnce, false, true).ConfigureAwait(false); - } while (!cancellationToken.IsCancellationRequested); + //wait for either the server to exit or the server to be primed + await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false); + + + //if the server has exited + async Task HandleServerCrashed(Task serverTask, bool isPrimary) + { + + int exitCode; + try + { + //nothing to do except try and reboot it + exitCode = await serverTask.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + return true; + } + await eventConsumer.HandleEvent(exitCode == 0 ? (isPrimary ? EventType.DDExit : EventType.DDOtherExit) : (isPrimary ? EventType.DDCrash : EventType.DDOtherCrash), null, cancellationToken).ConfigureAwait(false); + return false; + }; + + if (ddPrimaryTask.IsCompleted) + { + if (await HandleServerCrashed(ddPrimaryTask, true).ConfigureAwait(false)) + return; + ++retries; + continue; + } + + //start the secondary server + StartServer(initialLaunchParameters, false, out Task ddSecondaryTask, out CancellationTokenSource secondaryCts); + using (secondaryCts) + { + //now we wait for something to happen + await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, primaryRebootedTcs.Task).ConfigureAwait(false); + + + if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) + { + //catastrophic, start over + var t1 = HandleServerCrashed(ddPrimaryTask, secondaryIsOther); + var t2 = HandleServerCrashed(ddSecondaryTask, !secondaryIsOther); + await Task.WhenAll(t1, t2).ConfigureAwait(false); + if(t1.Result) + return; + ++retries; + continue; + } + + //crash of otherServer + if ((ddSecondaryTask.IsCompleted && secondaryIsOther) || (ddPrimaryTask.IsCompleted && !secondaryIsOther)) + { + + } + } + } + } while (true); } /// @@ -190,6 +294,8 @@ namespace Tgstation.Server.Host.Components /// public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { + if (launchParameters == null) + throw new ArgumentNullException(nameof(launchParameters)); Task launchTask; await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try @@ -210,6 +316,9 @@ namespace Tgstation.Server.Host.Components /// public async Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { + if (launchParameters == null) + throw new ArgumentNullException(nameof(launchParameters)); + TaskCompletionSource startupTcs; await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -219,14 +328,15 @@ namespace Tgstation.Server.Host.Components await eventConsumer.HandleEvent(EventType.DDLaunched, null, cancellationToken).ConfigureAwait(false); watchdogCancellationTokenSource?.Dispose(); watchdogCancellationTokenSource = new CancellationTokenSource(); - var startupTcs = new TaskCompletionSource(); - watchdogTask = Watchdog(currentLaunchParameters, startupTcs, watchdogCancellationTokenSource.Token); - await startupTcs.Task.ConfigureAwait(false); + startupTcs = new TaskCompletionSource(); + watchdogTask = Watchdog(startupTcs, watchdogCancellationTokenSource.Token); } finally { semaphore.Release(); } + //important to leave the lock so the watchdog can enter it + await startupTcs.Task.ConfigureAwait(false); } /// diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 56dc49d5e9..c56c2a6e4c 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -76,11 +76,7 @@ namespace Tgstation.Server.Host.Components await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - if (onSuccessfulStartup != null) - { - onSuccessfulStartup.SetResult(null); - onSuccessfulStartup = null; - } + onSuccessfulStartup?.SetResult(null); try { @@ -100,7 +96,7 @@ namespace Tgstation.Server.Host.Components } catch (Exception e) { - onSuccessfulStartup.SetException(e); + onSuccessfulStartup?.SetException(e); throw; } } diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs index 5be6d4617f..887d8ffe19 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -73,6 +73,14 @@ /// DDExit, /// + /// Parameters: Exit code + /// + DDOtherCrash, + /// + /// No parameters + /// + DDOtherExit, + /// /// No parameters /// DDRestart, diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByond.cs index 7f9d0ce98f..b49cdc826b 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByond.cs @@ -26,11 +26,12 @@ namespace Tgstation.Server.Host.Components /// /// Lock the current installation's location and run an /// + /// The return type of /// A taking the path to either dm.exe or dreamdaemon.exe and returning a /// Use the staged installation if possible /// Pass the path of dreamdaemon.exe to if dm.exe otherwise - /// A representing the running - Task UseExecutable(Func operation, bool stagedIfExists, bool dreamDaemon); + /// A resulting in the return type of + Task UseExecutable(Func> operation, bool stagedIfExists, bool dreamDaemon); /// /// Clears the cache folder diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/IDmbFactory.cs new file mode 100644 index 0000000000..640f8db7d3 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDmbFactory.cs @@ -0,0 +1,18 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Factory for s + /// + interface IDmbFactory + { + /// + /// Gets the next + /// + /// The for the operation + /// A representing the running operation + Task LockNextDmb(CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs new file mode 100644 index 0000000000..492282fe35 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -0,0 +1,25 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Provides absolute paths to the latest compiled .dmbs + /// + interface IDmbProvider : IDisposable + { + /// + /// The file name of the .dmb + /// + string DmbName { get; } + + /// + /// The primary game directory with a trailing directory separator + /// + string PrimaryDirectory { get; } + + /// + /// The secondary game directory with a trailing directory separator + /// + string SecondaryDirectory { get; } + } +} diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs index bc42312136..0b5cb09452 100644 --- a/src/Tgstation.Server.Host/Components/IInterop.cs +++ b/src/Tgstation.Server.Host/Components/IInterop.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components /// interface IInterop { - void SetServerControlHandler(Func serverControlHandler); + void SetServerControlHandler(Func serverControlHandler); void SetChatMessageHandler(Func chatMessageHandler); Version MinimumApiVersion { get; } @@ -17,7 +17,9 @@ namespace Tgstation.Server.Host.Components Task GetApiVersion(CancellationToken cancellationToken); - void SetRun(ushort? port, string accessToken); + void SetRun(ushort? port, string accessToken, bool primary); + + Task SwapPorts(ushort port, CancellationToken cancellationToken); Task ChatCommand(string command, string arguments, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/ServerControlEvent.cs b/src/Tgstation.Server.Host/Components/ServerControlEvent.cs new file mode 100644 index 0000000000..b619b68603 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ServerControlEvent.cs @@ -0,0 +1,20 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents a DreamDaemon control request or notification + /// + sealed class ServerControlEvent : EventArgs + { + /// + /// The + /// + public ServerControlEventType EventType { get; set; } + + /// + /// If the message was sent from the primary server + /// + public bool FromPrimaryServer { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs b/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs deleted file mode 100644 index 74275114bc..0000000000 --- a/src/Tgstation.Server.Host/Components/ServerControlEventArgs.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Tgstation.Server.Host.Components -{ - /// - /// Represents a DreamDaemon control request or notification - /// - sealed class ServerControlEventArgs : EventArgs - { - public bool PrimeReboot { get; set; } - - /// - /// If the event will result in a new run of the world - /// - public bool ServerReboot { get; set; } - - /// - /// If the event will result in a restart of the process. If both this and are , the server is being terminated - /// - public bool ProcessRestart { get; set; } - - /// - /// If the action to be taken is graceful - /// - public bool Graceful { get; set; } - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventType.cs b/src/Tgstation.Server.Host/Components/ServerControlEventType.cs new file mode 100644 index 0000000000..11a9e23bac --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ServerControlEventType.cs @@ -0,0 +1,21 @@ +namespace Tgstation.Server.Host.Components +{ + /// + /// Indicates a server control event from a DreamDaemon instance + /// + enum ServerControlEventType + { + /// + /// The server is loaded + /// + ServerPrimed, + /// + /// The server called world/Reboot() + /// + ServerRebooting, + /// + /// The server requested that it's process be terminated + /// + RequestedProcessTermination + } +} \ No newline at end of file From 532f037fead479d8905c6c47968b7f3f290f5568 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 26 Apr 2018 13:22:05 -0400 Subject: [PATCH 119/397] Hopefully got the watchdog figured out --- .../Components/DreamDaemon.cs | 106 +++++++++++------- .../Components/IInterop.cs | 4 +- 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index 329a84418e..cafc67a2df 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -84,14 +84,6 @@ namespace Tgstation.Server.Host.Components /// to complete when the primary server is primed /// TaskCompletionSource onPrimaryServerPrimed; - /// - /// to complete when the primary server is rebooted - /// - TaskCompletionSource onPrimaryServerRebooted; - /// - /// to complete when the secondary server is rebooted - /// - TaskCompletionSource onSecondaryServerRebooted; /// /// Construct @@ -156,9 +148,10 @@ namespace Tgstation.Server.Host.Components if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null) throw new InvalidOperationException("No byond version installed!"); await byond.ClearCache(cancellationToken).ConfigureAwait(false); - + var accessToken = cryptographySuite.GetSecureString(); + //lock the byond executable and run the server - async Task RunServer(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, string accessToken, bool isPrimary, CancellationToken serverCancellationToken) + async Task RunServer(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, bool isPrimary, CancellationToken serverCancellationToken) { using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) { @@ -171,9 +164,8 @@ namespace Tgstation.Server.Host.Components cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try { - var accessToken = cryptographySuite.GetSecureString(); var ddToken = cancellationTokenSource.Token; - ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, dreamDaemonPath, accessToken, isPrimary, ddToken), false, true); + ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, dreamDaemonPath, isPrimary, ddToken), false, true); interop.SetRun(isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken, isPrimary); } catch @@ -182,8 +174,7 @@ namespace Tgstation.Server.Host.Components throw; } }; - - bool secondaryIsOther = true; + var retries = 0; do { @@ -191,15 +182,12 @@ namespace Tgstation.Server.Host.Components await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); //load the event tcs' and get the initial launch parameters - var primaryRebootedTcs = new TaskCompletionSource(); var secondaryRebootedTcs = new TaskCompletionSource(); var primaryPrimedTcs = new TaskCompletionSource(); DreamDaemonLaunchParameters initialLaunchParameters; await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - onPrimaryServerRebooted = primaryRebootedTcs; - onSecondaryServerRebooted = secondaryRebootedTcs; onPrimaryServerPrimed = primaryPrimedTcs; initialLaunchParameters = currentLaunchParameters; } @@ -210,7 +198,7 @@ namespace Tgstation.Server.Host.Components //start the primary server StartServer(initialLaunchParameters, true, out Task ddPrimaryTask, out CancellationTokenSource primaryCts); - using (primaryCts) + try { //wait to make sure we got this far await onSuccessfulStartup.Task.ConfigureAwait(false); @@ -246,32 +234,70 @@ namespace Tgstation.Server.Host.Components continue; } - //start the secondary server - StartServer(initialLaunchParameters, false, out Task ddSecondaryTask, out CancellationTokenSource secondaryCts); - using (secondaryCts) + var launchParameters = initialLaunchParameters; + Task ddSecondaryTask = null; + CancellationTokenSource secondaryCts = null; + do { - //now we wait for something to happen - await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, primaryRebootedTcs.Task).ConfigureAwait(false); - - - if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) + if(ddSecondaryTask == null) + //start the secondary server + StartServer(initialLaunchParameters, false, out ddSecondaryTask, out secondaryCts); + try { - //catastrophic, start over - var t1 = HandleServerCrashed(ddPrimaryTask, secondaryIsOther); - var t2 = HandleServerCrashed(ddSecondaryTask, !secondaryIsOther); - await Task.WhenAll(t1, t2).ConfigureAwait(false); - if(t1.Result) - return; - ++retries; - continue; - } + //now we wait for something to happen + await Task.WhenAny(ddSecondaryTask, ddPrimaryTask).ConfigureAwait(false); - //crash of otherServer - if ((ddSecondaryTask.IsCompleted && secondaryIsOther) || (ddPrimaryTask.IsCompleted && !secondaryIsOther)) - { - + //crash of both servers + if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) + { + //catastrophic, start over + var t1 = HandleServerCrashed(ddPrimaryTask, interop.SecondaryIsOther); + var t2 = HandleServerCrashed(ddSecondaryTask, !interop.SecondaryIsOther); + await Task.WhenAll(t1, t2).ConfigureAwait(false); + if (t1.Result) + return; + ++retries; + continue; + } + + //activate the other server + await interop.SwapAndActivateServers(cancellationToken).ConfigureAwait(false); + + //load new launch parameters + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + launchParameters = currentLaunchParameters; + } + finally + { + semaphore.Release(); + } + + //crash of secondary server, just reboot it + if (ddSecondaryTask.IsCompleted) + { + await HandleServerCrashed(ddSecondaryTask, !interop.SecondaryIsOther).ConfigureAwait(false); + //restart the server + ddSecondaryTask = null; + } + //crash of primary server, bring it back + else + { + await HandleServerCrashed(ddPrimaryTask, interop.SecondaryIsOther).ConfigureAwait(false); + primaryCts.Dispose(); + StartServer(initialLaunchParameters, true, out ddPrimaryTask, out primaryCts); + } } - } + finally + { + secondaryCts.Dispose(); + } + } while (true); + } + finally + { + primaryCts.Dispose(); } } while (true); } diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs index 0b5cb09452..97a4153000 100644 --- a/src/Tgstation.Server.Host/Components/IInterop.cs +++ b/src/Tgstation.Server.Host/Components/IInterop.cs @@ -9,6 +9,8 @@ namespace Tgstation.Server.Host.Components /// interface IInterop { + bool SecondaryIsOther { get; set; } + void SetServerControlHandler(Func serverControlHandler); void SetChatMessageHandler(Func chatMessageHandler); @@ -19,7 +21,7 @@ namespace Tgstation.Server.Host.Components void SetRun(ushort? port, string accessToken, bool primary); - Task SwapPorts(ushort port, CancellationToken cancellationToken); + Task SwapAndActivateServers(CancellationToken cancellationToken); Task ChatCommand(string command, string arguments, CancellationToken cancellationToken); From 506192806cad6e8ac1b7c8c6926503fcbe24a1f7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 26 Apr 2018 14:09:14 -0400 Subject: [PATCH 120/397] WIP on DMAPI --- src/DMAPI/tgs/core/datum.dm | 2 +- src/DMAPI/tgs/includes.dm | 1 + src/DMAPI/tgs/v3210/api.dm | 2 +- src/DMAPI/tgs/v4/api.dm | 144 ++++++++++++++++++++++++++++++++++++ tgstation-server.sln | 6 ++ 5 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 src/DMAPI/tgs/v4/api.dm diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index 6af39a75df..f81569136c 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -3,7 +3,7 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null) /datum/tgs_api /datum/tgs_api/latest - parent_type = /datum/tgs_api/v3210 + parent_type = /datum/tgs_api/v4 TGS_PROTECT_DATUM(/datum/tgs_api) diff --git a/src/DMAPI/tgs/includes.dm b/src/DMAPI/tgs/includes.dm index 7ca906c840..b0ecec7f8e 100644 --- a/src/DMAPI/tgs/includes.dm +++ b/src/DMAPI/tgs/includes.dm @@ -4,3 +4,4 @@ #include "core\default_event_handler.dm" #include "v3210\api.dm" #include "v3210\commands.dm" +#include "v4\api.dm" diff --git a/src/DMAPI/tgs/v3210/api.dm b/src/DMAPI/tgs/v3210/api.dm index 5f69889f07..210d2ac88d 100644 --- a/src/DMAPI/tgs/v3210/api.dm +++ b/src/DMAPI/tgs/v3210/api.dm @@ -185,7 +185,7 @@ /datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only) ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]") -/datum/tgs_api/v3210/ChatPrivateMessage(message, admin_only) +/datum/tgs_api/v3210/ChatPrivateMessage(message, datum/tgs_chat_user/user) return TGS_UNIMPLEMENTED #undef REBOOT_MODE_NORMAL diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm new file mode 100644 index 0000000000..8ded8ee40a --- /dev/null +++ b/src/DMAPI/tgs/v4/api.dm @@ -0,0 +1,144 @@ +#define TGS4_API_VALIDATE "tgs_vali" +#define TGS4_PARAM_TOKEN "tgs_tok" +#define TGS4_PARAM_INSTANCE "tgs_inst_name" +#define TGS4_PARAM_INSTANCE_ID "tgs_inst_id" +#define TGS4_PARAM_REVISION_JSON "tgs_json" +#define TGS4_PARAM_HOST_PORT "tgs_port" +#define TGS4_PARAM_SECOND_PORT "tgs_port2" + +#define TGS4_TOPIC_COMMAND "tgs_com" + +#define TGS4_COMM_SERVER_PRIMED "tgs_prime" +#define TGS4_COMM_END_PROCESS "tgs_kill" +#define TGS4_COMM_CHAT_BROADCAST "tgs_chat" +#define TGS4_COMM_CHAT_PM "tgs_chat_pm" + +/datum/tgs_api/v4 + var/access_token + var/instance_id + var/instance_name + var/host_port + var/port_1 + var/port_2 + var/revision_json_path + +/datum/tgs_api/v4/ApiVersion() + return "4.0.0.0" + +/datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler) + access_token = world.params[TGS4_PARAM_TOKEN] + instance_id = world.params[TGS4_PARAM_INSTANCE_ID] + host_port = world.params[TGS4_PARAM_HOST_PORT] + if(world.params[TGS4_API_VALIDATE]) + Export(TGS4_API_VALIDATE) + del(world) + + instance_name = world.params[TGS4_PARAM_INSTANCE] + port_1 = world.port + port_2 = world.params[TGS4_PARAM_SECOND_PORT] + revision_json_path = world.params[TGS4_PARAM_REVISION_JSON] + +/datum/tgs_api/v4/OnInitializationComplete() + Export(TGS4_COMM_SERVER_PRIMED) + var/old_sleep_offline = world.sleep_offline + sleep(1) + world.sleep_offline = old_sleep_offline + +/datum/tgs_api/v4/OnTopic(T) + var/list/params = params2list(T) + var/their_sCK = params[TGS4_PARAM_TOKEN] + if(!their_sCK) + return FALSE //continue world/Topic + + if(their_sCK != access_token) + return "Invalid comms key!"; + + var/command = params[TGS4_TOPIC_COMMAND] + if(!command) + return "No command!" + + switch(command) + + return "Unknown command: [command]" + +/datum/tgs_api/v4/proc/Export(command) + return world.Export("http://127.0.0.1:[host_port]/Interop/[instance_id]?command=[command]&access_token=[access_token]") + +/datum/tgs_api/v4/OnReboot() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/v4/InstanceName() + return instance_name + +/datum/tgs_api/v4/TestMerges() + //do the best we can here as the datum can't be completed using the v3 api + . = list() + if(!revision_json_path || !fexists(revision_json_path)) + return + var/list/json = json_decode(file2text(revision_json_path)) + if(!json) + return + json = json["test_merges"] + for(var/I in json) + var/datum/tgs_revision_information/test_merge/tm = new + tm.number = text2num(I) + var/list/entry = json[I] + tm.pull_request_commit = entry["pr_commit"] + tm.author = entry["author"] + tm.title = entry["title"] + tm.commit = entry["commit"] + tm.commit = entry["origin_commit"] + tm.time_merged = text2num(entry["time_merged"]) + tm.comment = entry["comment"] + tm.url = entry["url"] + . += tm + +/datum/tgs_api/v4/EndProcess() + Export(TGS4_COMM_END_PROCESS) + +/datum/tgs_api/v4/Revision() + if(!revision_json_path || !fexists(revision_json_path)) + return + var/list/json = json_decode(file2text(revision_json_path)) + if(!json) + return + var/datum/tgs_revision_information + +/datum/tgs_api/v4/ChatChannelInfo() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/v4/ChatBroadcast(message, list/channels) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) + Export("[TGS_COMM_CHAT_PM] [user.id] [user.channel.id]") + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/tgstation-server.sln b/tgstation-server.sln index 3cd8efcb76..63e83585fe 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -93,6 +93,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{82066812 tests\DMAPI\travistester.dme = tests\DMAPI\travistester.dme EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v4", "v4", "{057FAC33-CC31-4948-91C6-B0977C335890}" + ProjectSection(SolutionItems) = preProject + src\DMAPI\tgs\v4\api.dm = src\DMAPI\tgs\v4\api.dm + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -217,6 +222,7 @@ Global {1B228ACB-60C3-4DF9-B716-9F0BD31F6766} = {F7765A4B-021C-454E-ABB1-2AB1B85F91B4} {3BB10856-AA01-43D1-AAB0-A013085426D6} = {7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC} {82066812-6C73-4360-943B-B23F2F491261} = {7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC} + {057FAC33-CC31-4948-91C6-B0977C335890} = {F7765A4B-021C-454E-ABB1-2AB1B85F91B4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DFD36C95-3E49-41C7-ACDB-86BAF5B18A79} From c63112aa3f3a263a48216dcc1310d264b3679cf7 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 28 Apr 2018 12:44:03 -0400 Subject: [PATCH 121/397] Remove legacy tgs version header --- src/DMAPI/tgs/core/core.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index ffeab4289b..faea16e3b9 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -15,6 +15,9 @@ TGS_WRITE_GLOBAL(tgs, new_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[0]) From 4f6595ba87264e8cc3928c5d1614a94558c8160a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 1 May 2018 17:45:41 -0400 Subject: [PATCH 122/397] Fix version indexing --- src/DMAPI/tgs/core/core.dm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index faea16e3b9..b15fd2c261 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -6,6 +6,7 @@ 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.") + return var/datum/tgs_api/new_api = new path TGS_INFO_LOG("Activated tgstation-server API for version [tgs_version]") @@ -20,10 +21,10 @@ var/list/version_bits = splittext(tgs_version, ".") - var/super = text2num(version_bits[0]) - var/major = text2num(version_bits[1]) - var/minor = text2num(version_bits[2]) - var/patch = text2num(version_bits[3]) + 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) From ce726e955590ea72014d125aaca9b00e5fe5c3dd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 2 May 2018 12:59:06 -0400 Subject: [PATCH 123/397] Done prototyping the v4 DMAPI --- src/DMAPI/tgs.dm | 6 +- src/DMAPI/tgs/includes.dm | 1 + src/DMAPI/tgs/v4/api.dm | 166 ++++++++++++++++++++++++++--------- src/DMAPI/tgs/v4/commands.dm | 72 +++++++++++++++ tgstation-server.sln | 1 + 5 files changed, 203 insertions(+), 43 deletions(-) create mode 100644 src/DMAPI/tgs/v4/commands.dm diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index e885b06397..e68bfa9b61 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -44,6 +44,8 @@ //EVENT CODES +#define TGS_EVENT_PORT_SWAP 1 //before a port change is about to happen, extra parameter is new port + //TODO //REQUIRED HOOKS @@ -101,7 +103,9 @@ var/datum/tgs_chat_channel/channel //The /datum/tgs_chat_channel this user was from //user definable callback for handling events -/datum/tgs_event_handler/proc/HandleEvent(event_code) +//extra parameters may be specified depending on the event +/datum/tgs_event_handler/proc/HandleEvent(event_code, ...) + set waitfor = FALSE return //user definable chat command diff --git a/src/DMAPI/tgs/includes.dm b/src/DMAPI/tgs/includes.dm index b0ecec7f8e..2fdd5ed1e0 100644 --- a/src/DMAPI/tgs/includes.dm +++ b/src/DMAPI/tgs/includes.dm @@ -5,3 +5,4 @@ #include "v3210\api.dm" #include "v3210\commands.dm" #include "v4\api.dm" +#include "v4\commands.dm" diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 8ded8ee40a..685d691d21 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -1,17 +1,18 @@ -#define TGS4_API_VALIDATE "tgs_vali" -#define TGS4_PARAM_TOKEN "tgs_tok" -#define TGS4_PARAM_INSTANCE "tgs_inst_name" -#define TGS4_PARAM_INSTANCE_ID "tgs_inst_id" -#define TGS4_PARAM_REVISION_JSON "tgs_json" -#define TGS4_PARAM_HOST_PORT "tgs_port" -#define TGS4_PARAM_SECOND_PORT "tgs_port2" +#define TGS4_PARAM_INFO_JSON "tgs_json" #define TGS4_TOPIC_COMMAND "tgs_com" +#define TGS4_TOPIC_TOKEN "tgs_tok" +#define TGS4_TOPIC_SUCCESS "tgs_success" +#define TGS4_TOPIC_SWAP "tgs_swap" +#define TGS4_TOPIC_SWAP_DELAYED "tgs_swap_delayed" +#define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm" +#define TGS4_TOPIC_EVENT "tgs_event" +#define TGS4_COMM_VALIDATE "tgs_vali" #define TGS4_COMM_SERVER_PRIMED "tgs_prime" +#define TGS4_COMM_SERVER_REBOOT "tgs_reboot" #define TGS4_COMM_END_PROCESS "tgs_kill" -#define TGS4_COMM_CHAT_BROADCAST "tgs_chat" -#define TGS4_COMM_CHAT_PM "tgs_chat_pm" +#define TGS4_COMM_CHAT "tgs_chat_send" /datum/tgs_api/v4 var/access_token @@ -20,33 +21,57 @@ var/host_port var/port_1 var/port_2 - var/revision_json_path + var/cached_json + var/json_path + + var/list/custom_commands + + var/list/cached_test_merges + var/datum/tgs_revision_information/cached_revision + + var/datum/tgs_event_handler/event_handler /datum/tgs_api/v4/ApiVersion() return "4.0.0.0" /datum/tgs_api/v4/OnWorldNew(datum/tgs_event_handler/event_handler) - access_token = world.params[TGS4_PARAM_TOKEN] - instance_id = world.params[TGS4_PARAM_INSTANCE_ID] - host_port = world.params[TGS4_PARAM_HOST_PORT] - if(world.params[TGS4_API_VALIDATE]) - Export(TGS4_API_VALIDATE) + json_path = world.params[TGS4_PARAM_INFO_JSON] + if(!json_path) + TGS_ERROR_LOG("Missing [TGS4_PARAM_INFO_JSON] world parameter!") + return + var/json_file = file2text(json_path) + if(!json_file) + TGS_ERROR_LOG("Missing specified json file: [json_path]") + return + cached_json = json_decode(json_file) + if(!cached_json) + TGS_ERROR_LOG("Failed to decode info json: [json_file]") + return + + access_token = cached_json["access_token"] + instance_id = text2num(cached_json["instance_id"]) + host_port = text2num(cached_json["host_port"]) + if(cached_json["api_validate_only"]) + Export(TGS4_COMM_VALIDATE) del(world) - instance_name = world.params[TGS4_PARAM_INSTANCE] + src.event_handler = event_handler + instance_name = cached_json["instance_name"] port_1 = world.port - port_2 = world.params[TGS4_PARAM_SECOND_PORT] - revision_json_path = world.params[TGS4_PARAM_REVISION_JSON] + port_2 = cached_json["next_port"] + ListCustomCommands() /datum/tgs_api/v4/OnInitializationComplete() Export(TGS4_COMM_SERVER_PRIMED) + var/tgs4_secret_sleep_offline_sauce = 24051994 var/old_sleep_offline = world.sleep_offline sleep(1) - world.sleep_offline = old_sleep_offline + if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it + world.sleep_offline = old_sleep_offline /datum/tgs_api/v4/OnTopic(T) var/list/params = params2list(T) - var/their_sCK = params[TGS4_PARAM_TOKEN] + var/their_sCK = params[TGS4_TOPIC_TOKEN] if(!their_sCK) return FALSE //continue world/Topic @@ -58,27 +83,48 @@ return "No command!" switch(command) + if(TGS4_TOPIC_SWAP) + SwapPorts(FALSE) + return TGS4_TOPIC_SUCCESS + if(TGS4_TOPIC_SWAP_DELAYED) + SwapPorts(TRUE) + return TGS4_TOPIC_SUCCESS + if(TGS4_TOPIC_CHAT_COMMAND) + var/result = HandleCustomCommand(params[TGS4_TOPIC_CHAT_COMMAND]) + if(!result) + return json_encode(list("error" = "Error running chat command!")) + return result + if(TGS4_TOPIC_EVENT) + event_handler.HandleEvent(text2num(params[TGS4_TOPIC_EVENT])) + return TGS4_TOPIC_SUCCESS return "Unknown command: [command]" +/datum/tgs_api/v4/proc/SwapPorts(delayed) + set waitfor = FALSE + var/new_port = world.port == port_1 ? port_2 : port_1 + event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) + world.OpenPort("none") //close the port + if(delayed) + sleep(50) //wait for other server to close port + world.OpenPort(new_port) + /datum/tgs_api/v4/proc/Export(command) - return world.Export("http://127.0.0.1:[host_port]/Interop/[instance_id]?command=[command]&access_token=[access_token]") + return world.Export("http://127.0.0.1:[host_port]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]") /datum/tgs_api/v4/OnReboot() - return TGS_UNIMPLEMENTED + Export(TGS4_COMM_SERVER_REBOOT) /datum/tgs_api/v4/InstanceName() return instance_name /datum/tgs_api/v4/TestMerges() - //do the best we can here as the datum can't be completed using the v3 api + if(cached_test_merges) + return cached_test_merges + . = list() - if(!revision_json_path || !fexists(revision_json_path)) - return - var/list/json = json_decode(file2text(revision_json_path)) - if(!json) - return - json = json["test_merges"] + cached_test_merges = . + var/json = cached_json["test_merges"] for(var/I in json) var/datum/tgs_revision_information/test_merge/tm = new tm.number = text2num(I) @@ -87,7 +133,7 @@ tm.author = entry["author"] tm.title = entry["title"] tm.commit = entry["commit"] - tm.commit = entry["origin_commit"] + tm.origin_commit = entry["origin_commit"] tm.time_merged = text2num(entry["time_merged"]) tm.comment = entry["comment"] tm.url = entry["url"] @@ -97,24 +143,60 @@ Export(TGS4_COMM_END_PROCESS) /datum/tgs_api/v4/Revision() - if(!revision_json_path || !fexists(revision_json_path)) - return - var/list/json = json_decode(file2text(revision_json_path)) - if(!json) - return - var/datum/tgs_revision_information - -/datum/tgs_api/v4/ChatChannelInfo() - return TGS_UNIMPLEMENTED + if(!cached_revision) + var/json = cached_json["revision"] + cached_revision = new + cached_revision.commit = json["commit"] + cached_revision.origin_commit = json["origin_commit"] + return cached_revision /datum/tgs_api/v4/ChatBroadcast(message, list/channels) - return TGS_UNIMPLEMENTED + var/list/ids + if(length(channels)) + ids = list() + for(var/I in channels) + var/datum/tgs_chat_channel/channel = I + ids += channel.id + message = list("message" = message, "channels" = ids) + Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) - return TGS_UNIMPLEMENTED + message = list("message" = message, "channels" = admin_only ? "admin" : "game") + Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) - Export("[TGS_COMM_CHAT_PM] [user.id] [user.channel.id]") + message = list("message" = message, "user" = list("id" = user.id, "channel" = user.channel.id)) + Export("[TGS4_COMM_CHAT] [json_encode(message)]") + +/datum/tgs_api/v4/ChatChannelInfo() + . = list() + //no caching cause tgs may change this + var/chat_json_path = cached_json["chat_channels_json"] + var/list/json = json_decode(file2text(chat_json_path)) + for(var/I in json) + var/datum/tgs_chat_channel/channel = new + channel.id = I["id"] + channel.friendly_name = I["friendly_name"] + channel.server_name = I["server_name"] + channel.provider_name = I["provider_name"] + channel.is_admin_channel = I["is_admin_channel"] + channel.is_private_channel = FALSE //tgs will never send us pm channels + . += channel + +#undef TGS4_TOPIC_COMMAND +#undef TGS4_TOPIC_TOKEN +#undef TGS4_TOPIC_SUCCESS +#undef TGS4_TOPIC_SWAP +#undef TGS4_TOPIC_SWAP_DELAYED +#undef TGS4_TOPIC_CHAT_COMMAND +#undef TGS4_TOPIC_EVENT + +#undef TGS4_COMM_SERVER_PRIMED +#undef TGS4_COMM_SERVER_REBOOT +#undef TGS4_COMM_END_PROCESS +#undef TGS4_COMM_CHAT + +#undef TGS4_COMM_VALIDATE /* The MIT License diff --git a/src/DMAPI/tgs/v4/commands.dm b/src/DMAPI/tgs/v4/commands.dm new file mode 100644 index 0000000000..bf32f360e3 --- /dev/null +++ b/src/DMAPI/tgs/v4/commands.dm @@ -0,0 +1,72 @@ +/datum/tgs_api/v4/proc/ListCustomCommands() + var/results = list() + custom_commands = list() + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) + var/datum/tgs_chat_command/stc = new I + var/command_name = stc.name + if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) + TGS_ERROR_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!") + continue + + if(results[command_name]) + var/datum/other = custom_commands[command_name] + TGS_ERROR_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!") + continue + results[command_name] = list("help_text" = stc.help_text, "admin_only" = stc.admin_only) + custom_commands[command_name] = stc + + var/commands_file = cached_json["chat_commands_file"] + if(!commands_file) + return + text2file(json_encode(results), commands_file) + +/datum/tgs_api/v4/proc/HandleCustomCommand(command_json) + var/list/data = json_decode(command_json) + var/command = data["command"] + var/user = data["user"] + var/params = data["params"] + + var/datum/tgs_chat_user/u = new + u.id = user["id"] + u.friendly_name = user["friendly_name"] + u.mention = user["mention"] + var/datum/tgs_chat_channel/channel = new + u.channel = channel + var/channel_json = user["channel"] + channel.id = channel_json["id"] + channel.friendly_name = channel_json["friendly_name"] + channel.server_name = channel_json["server_name"] + channel.is_admin_channel = channel_json["is_admin_channel"] + channel.is_private_channel = channel_json["is_private_channel"] + + var/datum/tgs_chat_command/sc = custom_commands[command] + var/result = sc.Run(u, params) + return json_encode(list("result" = result)) + +/* + +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/tgstation-server.sln b/tgstation-server.sln index 63e83585fe..7ceda39bca 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -96,6 +96,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v4", "v4", "{057FAC33-CC31-4948-91C6-B0977C335890}" ProjectSection(SolutionItems) = preProject src\DMAPI\tgs\v4\api.dm = src\DMAPI\tgs\v4\api.dm + src\DMAPI\tgs\v4\commands.dm = src\DMAPI\tgs\v4\commands.dm EndProjectSection EndProject Global From 3820a4f0b79f27c89acdf2c5e7a85a85d39ef1a0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 2 May 2018 15:02:06 -0400 Subject: [PATCH 124/397] On DMB update --- src/DMAPI/tgs/v4/api.dm | 7 +- .../Components/DreamDaemon.cs | 67 ++++++++++++++----- .../Components/IDmbFactory.cs | 6 ++ .../Components/IDmbProvider.cs | 1 + .../Components/IInterop.cs | 7 +- 5 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 685d691d21..e5e40bb9eb 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -113,7 +113,12 @@ return world.Export("http://127.0.0.1:[host_port]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]") /datum/tgs_api/v4/OnReboot() - Export(TGS4_COMM_SERVER_REBOOT) + var/json = Export(TGS4_COMM_SERVER_REBOOT) + var/list/result = json_decode(json) + if(!result) + return + if(json["port_swap"]) + SwapPorts(json["delayed"]) /datum/tgs_api/v4/InstanceName() return instance_name diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index cafc67a2df..d90137ae82 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -204,10 +204,9 @@ namespace Tgstation.Server.Host.Components await onSuccessfulStartup.Task.ConfigureAwait(false); onSuccessfulStartup = null; - //wait for either the server to exit or the server to be primed + //wait for either the server to exit or be primed await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false); - //if the server has exited async Task HandleServerCrashed(Task serverTask, bool isPrimary) { @@ -237,15 +236,52 @@ namespace Tgstation.Server.Host.Components var launchParameters = initialLaunchParameters; Task ddSecondaryTask = null; CancellationTokenSource secondaryCts = null; - do + try { - if(ddSecondaryTask == null) - //start the secondary server - StartServer(initialLaunchParameters, false, out ddSecondaryTask, out secondaryCts); - try + do { + if (ddSecondaryTask == null) + //start the secondary server + StartServer(initialLaunchParameters, false, out ddSecondaryTask, out secondaryCts); + + var newDmbTask = dmbFactory.OnNewerDmb(); + //now we wait for something to happen - await Task.WhenAny(ddSecondaryTask, ddPrimaryTask).ConfigureAwait(false); + await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, newDmbTask).ConfigureAwait(false); + + if (newDmbTask.IsCompleted) + { + //restart the other server but don't treat it as an error + + //load new launch parameters + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + launchParameters = currentLaunchParameters; + } + finally + { + semaphore.Release(); + } + + //restart other server + if (interop.SecondaryIsOther) + { + secondaryCts.Cancel(); + await ddSecondaryTask.ConfigureAwait(false); + ddSecondaryTask = null; + secondaryCts.Dispose(); + continue; + } + else + { + primaryCts.Cancel(); + await ddPrimaryTask.ConfigureAwait(false); + primaryCts.Dispose(); + StartServer(initialLaunchParameters, true, out ddPrimaryTask, out primaryCts); + continue; + } + } //crash of both servers if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) @@ -261,7 +297,7 @@ namespace Tgstation.Server.Host.Components } //activate the other server - await interop.SwapAndActivateServers(cancellationToken).ConfigureAwait(false); + await interop.ActivateOtherServer(cancellationToken).ConfigureAwait(false); //load new launch parameters await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -278,6 +314,7 @@ namespace Tgstation.Server.Host.Components if (ddSecondaryTask.IsCompleted) { await HandleServerCrashed(ddSecondaryTask, !interop.SecondaryIsOther).ConfigureAwait(false); + secondaryCts.Dispose(); //restart the server ddSecondaryTask = null; } @@ -288,12 +325,12 @@ namespace Tgstation.Server.Host.Components primaryCts.Dispose(); StartServer(initialLaunchParameters, true, out ddPrimaryTask, out primaryCts); } - } - finally - { - secondaryCts.Dispose(); - } - } while (true); + } while (true); + } + finally + { + secondaryCts?.Dispose(); + } } finally { diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/IDmbFactory.cs index 640f8db7d3..2ed1a771ff 100644 --- a/src/Tgstation.Server.Host/Components/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/IDmbFactory.cs @@ -14,5 +14,11 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// A representing the running operation Task LockNextDmb(CancellationToken cancellationToken); + + /// + /// Get a that completes when the result of a call to will be different than the previous call if any + /// + /// A representing the running operation + Task OnNewerDmb(); } } diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs index 492282fe35..b0d190ab34 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs index 97a4153000..3c6ba60b75 100644 --- a/src/Tgstation.Server.Host/Components/IInterop.cs +++ b/src/Tgstation.Server.Host/Components/IInterop.cs @@ -14,17 +14,12 @@ namespace Tgstation.Server.Host.Components void SetServerControlHandler(Func serverControlHandler); void SetChatMessageHandler(Func chatMessageHandler); - Version MinimumApiVersion { get; } - Version MaximumApiVersion { get; } - Task GetApiVersion(CancellationToken cancellationToken); void SetRun(ushort? port, string accessToken, bool primary); - Task SwapAndActivateServers(CancellationToken cancellationToken); + Task ActivateOtherServer(CancellationToken cancellationToken); Task ChatCommand(string command, string arguments, CancellationToken cancellationToken); - - Task PromptChatResponse(EventType eventType, CancellationToken cancellationToken); } } From d6d62b278ba09b34052a705ffba918501d53cced Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 2 May 2018 16:15:29 -0400 Subject: [PATCH 125/397] Refactor Watchdog out of DreamDaemon --- .../Components/DreamDaemon.cs | 287 ++-------------- .../Components/IInterop.cs | 3 +- .../Components/ILaunchParametersFactory.cs | 19 ++ .../Components/IWatchdog.cs | 25 ++ .../Components/Watchdog.cs | 309 ++++++++++++++++++ 5 files changed, 381 insertions(+), 262 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/ILaunchParametersFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/IWatchdog.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog.cs diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index d90137ae82..04055e8757 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -3,12 +3,11 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components { /// - sealed class DreamDaemon : IDreamDaemon, IDisposable + sealed class DreamDaemon : IDreamDaemon, ILaunchParametersFactory, IDisposable { /// public bool Running { get; private set; } @@ -33,29 +32,13 @@ namespace Tgstation.Server.Host.Components /// readonly IEventConsumer eventConsumer; /// - /// The for - /// - readonly IByond byond; - /// - /// The for - /// - readonly ICryptographySuite cryptographySuite; - /// /// The for /// readonly IInterop interop; /// - /// The for + /// The for /// - readonly IInstanceShutdownMethod instanceShutdownMethod; - /// - /// The for - /// - readonly IDreamDaemonExecutor dreamDaemonExecutor; - /// - /// The for - /// - readonly IDmbFactory dmbFactory; + readonly IWatchdog watchdog; /// /// Used for write control to class variables @@ -72,39 +55,18 @@ namespace Tgstation.Server.Host.Components /// DreamDaemonLaunchParameters currentLaunchParameters; - /// - /// The for - /// - CancellationTokenSource watchdogCancellationTokenSource; - /// - /// The monitor for the DD process - /// - Task watchdogTask; - /// - /// to complete when the primary server is primed - /// - TaskCompletionSource onPrimaryServerPrimed; - /// /// Construct /// /// The value of - /// The value of - /// The value of /// The value of - /// The value of - /// The value of - /// The value of + /// The value of /// The initial value of and - public DreamDaemon(IEventConsumer eventConsumer, IByond byond, ICryptographySuite cryptographySuite, IInterop interop, IInstanceShutdownMethod instanceShutdownMethod, IDreamDaemonExecutor dreamDaemonExecutor, IDmbFactory dmbFactory, DreamDaemonSettings initialSettings) + public DreamDaemon(IEventConsumer eventConsumer, IInterop interop, IWatchdog watchdog, DreamDaemonSettings initialSettings) { this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); - this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); - this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); - this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); - this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); - this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); + this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); interop.SetServerControlHandler(OnServerControl); @@ -115,12 +77,7 @@ namespace Tgstation.Server.Host.Components } /// - public void Dispose() - { - if (watchdogCancellationTokenSource != null) - watchdogCancellationTokenSource.Dispose(); - semaphore.Dispose(); - } + public void Dispose() => semaphore.Dispose(); /// /// Handler for server control events @@ -137,208 +94,6 @@ namespace Tgstation.Server.Host.Components throw new NotImplementedException(); } - /// - /// Main DD execution and monitoring - /// - /// The to be completed when the server initially starts - /// The for the operation - /// A representing the running operation - async Task Watchdog(TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) - { - if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null) - throw new InvalidOperationException("No byond version installed!"); - await byond.ClearCache(cancellationToken).ConfigureAwait(false); - var accessToken = cryptographySuite.GetSecureString(); - - //lock the byond executable and run the server - async Task RunServer(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, bool isPrimary, CancellationToken serverCancellationToken) - { - using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) - { - return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, String.Concat(dmb.PrimaryDirectory, dmb.DmbName), accessToken, isPrimary, serverCancellationToken).ConfigureAwait(false); - } - }; - - void StartServer(DreamDaemonLaunchParameters launchParameters, bool isPrimary, out Task ddTask, out CancellationTokenSource cancellationTokenSource) - { - cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - try - { - var ddToken = cancellationTokenSource.Token; - ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, dreamDaemonPath, isPrimary, ddToken), false, true); - interop.SetRun(isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken, isPrimary); - } - catch - { - cancellationTokenSource.Dispose(); - throw; - } - }; - - var retries = 0; - do - { - var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour - await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); - - //load the event tcs' and get the initial launch parameters - var secondaryRebootedTcs = new TaskCompletionSource(); - var primaryPrimedTcs = new TaskCompletionSource(); - DreamDaemonLaunchParameters initialLaunchParameters; - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - onPrimaryServerPrimed = primaryPrimedTcs; - initialLaunchParameters = currentLaunchParameters; - } - finally - { - semaphore.Release(); - } - - //start the primary server - StartServer(initialLaunchParameters, true, out Task ddPrimaryTask, out CancellationTokenSource primaryCts); - try - { - //wait to make sure we got this far - await onSuccessfulStartup.Task.ConfigureAwait(false); - onSuccessfulStartup = null; - - //wait for either the server to exit or be primed - await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false); - - //if the server has exited - async Task HandleServerCrashed(Task serverTask, bool isPrimary) - { - - int exitCode; - try - { - //nothing to do except try and reboot it - exitCode = await serverTask.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - return true; - } - await eventConsumer.HandleEvent(exitCode == 0 ? (isPrimary ? EventType.DDExit : EventType.DDOtherExit) : (isPrimary ? EventType.DDCrash : EventType.DDOtherCrash), null, cancellationToken).ConfigureAwait(false); - return false; - }; - - if (ddPrimaryTask.IsCompleted) - { - if (await HandleServerCrashed(ddPrimaryTask, true).ConfigureAwait(false)) - return; - ++retries; - continue; - } - - var launchParameters = initialLaunchParameters; - Task ddSecondaryTask = null; - CancellationTokenSource secondaryCts = null; - try - { - do - { - if (ddSecondaryTask == null) - //start the secondary server - StartServer(initialLaunchParameters, false, out ddSecondaryTask, out secondaryCts); - - var newDmbTask = dmbFactory.OnNewerDmb(); - - //now we wait for something to happen - await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, newDmbTask).ConfigureAwait(false); - - if (newDmbTask.IsCompleted) - { - //restart the other server but don't treat it as an error - - //load new launch parameters - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - launchParameters = currentLaunchParameters; - } - finally - { - semaphore.Release(); - } - - //restart other server - if (interop.SecondaryIsOther) - { - secondaryCts.Cancel(); - await ddSecondaryTask.ConfigureAwait(false); - ddSecondaryTask = null; - secondaryCts.Dispose(); - continue; - } - else - { - primaryCts.Cancel(); - await ddPrimaryTask.ConfigureAwait(false); - primaryCts.Dispose(); - StartServer(initialLaunchParameters, true, out ddPrimaryTask, out primaryCts); - continue; - } - } - - //crash of both servers - if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) - { - //catastrophic, start over - var t1 = HandleServerCrashed(ddPrimaryTask, interop.SecondaryIsOther); - var t2 = HandleServerCrashed(ddSecondaryTask, !interop.SecondaryIsOther); - await Task.WhenAll(t1, t2).ConfigureAwait(false); - if (t1.Result) - return; - ++retries; - continue; - } - - //activate the other server - await interop.ActivateOtherServer(cancellationToken).ConfigureAwait(false); - - //load new launch parameters - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - launchParameters = currentLaunchParameters; - } - finally - { - semaphore.Release(); - } - - //crash of secondary server, just reboot it - if (ddSecondaryTask.IsCompleted) - { - await HandleServerCrashed(ddSecondaryTask, !interop.SecondaryIsOther).ConfigureAwait(false); - secondaryCts.Dispose(); - //restart the server - ddSecondaryTask = null; - } - //crash of primary server, bring it back - else - { - await HandleServerCrashed(ddPrimaryTask, interop.SecondaryIsOther).ConfigureAwait(false); - primaryCts.Dispose(); - StartServer(initialLaunchParameters, true, out ddPrimaryTask, out primaryCts); - } - } while (true); - } - finally - { - secondaryCts?.Dispose(); - } - } - finally - { - primaryCts.Dispose(); - } - } while (true); - } - /// public async Task CancelGracefulActions(CancellationToken cancellationToken) { @@ -381,25 +136,22 @@ namespace Tgstation.Server.Host.Components { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); - TaskCompletionSource startupTcs; await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + Task watchdogStartup; try { if (Running) throw new InvalidOperationException("DreamDaemon already running!"); Running = true; await eventConsumer.HandleEvent(EventType.DDLaunched, null, cancellationToken).ConfigureAwait(false); - watchdogCancellationTokenSource?.Dispose(); - watchdogCancellationTokenSource = new CancellationTokenSource(); - startupTcs = new TaskCompletionSource(); - watchdogTask = Watchdog(startupTcs, watchdogCancellationTokenSource.Token); + watchdogStartup = watchdog.Start(this, cancellationToken); } finally { semaphore.Release(); } //important to leave the lock so the watchdog can enter it - await startupTcs.Task.ConfigureAwait(false); + await watchdogStartup.ConfigureAwait(false); } /// @@ -419,7 +171,7 @@ namespace Tgstation.Server.Host.Components } await eventConsumer.HandleEvent(EventType.DDRestart, null, cancellationToken).ConfigureAwait(false); if (Running) - watchdogCancellationTokenSource.Cancel(); + await watchdog.Stop().ConfigureAwait(false); await Launch(currentLaunchParameters, cancellationToken).ConfigureAwait(false); } finally @@ -449,8 +201,21 @@ namespace Tgstation.Server.Host.Components return; } await eventConsumer.HandleEvent(EventType.DDTerminated, null, cancellationToken).ConfigureAwait(false); - watchdogCancellationTokenSource.Cancel(); - await watchdogTask.ConfigureAwait(false); + await watchdog.Stop().ConfigureAwait(false); + } + finally + { + semaphore.Release(); + } + } + + /// + public async Task GetLaunchParameters(CancellationToken cancellationToken) + { + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + return currentLaunchParameters; } finally { diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs index 3c6ba60b75..32edd2e047 100644 --- a/src/Tgstation.Server.Host/Components/IInterop.cs +++ b/src/Tgstation.Server.Host/Components/IInterop.cs @@ -21,5 +21,6 @@ namespace Tgstation.Server.Host.Components Task ActivateOtherServer(CancellationToken cancellationToken); Task ChatCommand(string command, string arguments, CancellationToken cancellationToken); - } + void OnServerPrimed(Action actionToTake); + } } diff --git a/src/Tgstation.Server.Host/Components/ILaunchParametersFactory.cs b/src/Tgstation.Server.Host/Components/ILaunchParametersFactory.cs new file mode 100644 index 0000000000..ee2a67ec21 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ILaunchParametersFactory.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components +{ + /// + /// For retrieving current + /// + interface ILaunchParametersFactory + { + /// + /// Get the latest + /// + /// The for the operation + /// A resulting in the latest + Task GetLaunchParameters(CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/IWatchdog.cs b/src/Tgstation.Server.Host/Components/IWatchdog.cs new file mode 100644 index 0000000000..59882f6c89 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IWatchdog.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// For monitoring DreamDaemon uptime + /// + interface IWatchdog + { + /// + /// Start the + /// + /// The for the run + /// The for the operation + /// A representing the running operation + Task Start(ILaunchParametersFactory launchParametersFactory, CancellationToken cancellationToken); + + /// + /// Stop the + /// + /// A representing the running operation + Task Stop(); + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs new file mode 100644 index 0000000000..3213641a92 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -0,0 +1,309 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class Watchdog : IWatchdog, IDisposable + { + /// + /// The for the + /// + readonly IByond byond; + /// + /// The for the + /// + readonly IDreamDaemonExecutor dreamDaemonExecutor; + /// + /// The for the + /// + readonly IInterop interop; + /// + /// The for the + /// + readonly IDmbFactory dmbFactory; + /// + /// The for the + /// + readonly ICryptographySuite cryptographySuite; + /// + /// The for the + /// + readonly IEventConsumer eventConsumer; + + /// + /// Represents the currently running + /// + Task watchdogTask; + /// + /// for + /// + CancellationTokenSource watchdogCancellationTokenSource; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + public Watchdog(IByond byond, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, ICryptographySuite cryptographySuite, IEventConsumer eventConsumer) + { + this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); + this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); + this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); + this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + } + + /// + public void Dispose() => watchdogCancellationTokenSource?.Dispose(); + + /// + /// Loads a dmb and runs it through + /// + /// The for the run + /// The to be completed once the server starts if any + /// The access token for the server + /// The path to the DreamDaemon executable + /// If a primary server is being launched + /// The for the operation + /// A resulting in the exit code of DreamDaemon + async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string accessToken, string dreamDaemonPath, bool isPrimary, CancellationToken cancellationToken) + { + using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) + return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, String.Concat(dmb.PrimaryDirectory, dmb.DmbName), accessToken, isPrimary, cancellationToken).ConfigureAwait(false); + } + + /// + /// Locks in a version and runs a server through + /// + /// The for the run + /// The to be completed once the server starts if any + /// The access token for the server + /// If a primary server is being launched + /// The for the operation + /// A tied to the lifetime of the resulting + /// A resulting in the exit code of DreamDaemon + Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string accessToken, bool isPrimary, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) + { + cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + try + { + var ddToken = cancellationTokenSource.Token; + var ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, onSuccessfulStartup, accessToken, dreamDaemonPath, isPrimary, ddToken), false, true); + interop.SetRun(isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken, isPrimary); + return ddTask; + } + catch + { + cancellationTokenSource.Dispose(); + throw; + } + } + + /// + /// Handle a crash or exit of a server + /// + /// The resulting in the exit code of the server + /// If the ended server was the primary server + /// The for the operation + /// A resulting in if the server was ended due to a , otherwise + async Task HandleServerCrashed(Task serverTask, bool isPrimary, CancellationToken cancellationToken) + { + int exitCode; + try + { + //nothing to do except try and reboot it + exitCode = await serverTask.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + return true; + } + await eventConsumer.HandleEvent(exitCode == 0 ? (isPrimary ? EventType.DDExit : EventType.DDOtherExit) : (isPrimary ? EventType.DDCrash : EventType.DDOtherCrash), null, cancellationToken).ConfigureAwait(false); + return false; + } + + /// + /// Main loop + /// + /// The for the run + /// The to be completed once the first server starts + /// The for the operation + /// A representing the running operation + async Task Run(ILaunchParametersFactory launchParametersFactory, TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) + { + if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null) + throw new InvalidOperationException("No byond version installed!"); + await byond.ClearCache(cancellationToken).ConfigureAwait(false); + var accessToken = cryptographySuite.GetSecureString(); + + var retries = 0; + do + { + var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour + await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); + + //load the event tcs' and get the initial launch parameters + var primaryPrimedTcs = new TaskCompletionSource(); + interop.OnServerPrimed(() => primaryPrimedTcs.SetResult(null)); + var initialLaunchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); + //start the primary server + var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, accessToken, true, cancellationToken, out CancellationTokenSource primaryCts); + try + { + //wait to make sure we got this far + await onSuccessfulStartup.Task.ConfigureAwait(false); + onSuccessfulStartup = null; + + //wait for either the server to exit or be primed + await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false); + + if (ddPrimaryTask.IsCompleted) + { + if (await HandleServerCrashed(ddPrimaryTask, true, cancellationToken).ConfigureAwait(false)) + return; + ++retries; + continue; + } + + var launchParameters = initialLaunchParameters; + Task ddSecondaryTask = null; + CancellationTokenSource secondaryCts = null; + try + { + do + { + if (ddSecondaryTask == null) + //start the secondary server + ddSecondaryTask = StartServer(initialLaunchParameters, null, accessToken, false, cancellationToken, out secondaryCts); + + var newDmbTask = dmbFactory.OnNewerDmb(); + + //now we wait for something to happen + await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, newDmbTask).ConfigureAwait(false); + + //some helpers + void PrimaryRestart() + { + primaryCts.Dispose(); + ddPrimaryTask = StartServer(initialLaunchParameters, null, accessToken, true, cancellationToken, out primaryCts); + } + void SecondaryRestart() + { + ddSecondaryTask = null; + secondaryCts.Dispose(); + }; + Task PrimaryCrash() => HandleServerCrashed(ddPrimaryTask, interop.SecondaryIsOther, cancellationToken); + Task SecondaryCrash() => HandleServerCrashed(ddSecondaryTask, !interop.SecondaryIsOther, cancellationToken); + + //update available + if (newDmbTask.IsCompleted) + { + //restart the other server but don't treat it as an error + launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); + //restart other server + if (interop.SecondaryIsOther) + { + secondaryCts.Cancel(); + await ddSecondaryTask.ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + SecondaryRestart(); + } + else + { + primaryCts.Cancel(); + await ddPrimaryTask.ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + PrimaryRestart(); + } + continue; + } + + //crash of both servers + if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) + { + //catastrophic, start over + var t1 = PrimaryCrash(); + await Task.WhenAll(t1, SecondaryCrash()).ConfigureAwait(false); + if (t1.Result) + return; + ++retries; + continue; + } + + //below this point: crash of single server + + //activate the other server and load new launch params + var otherServerActivation = interop.ActivateOtherServer(cancellationToken); + launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); + await otherServerActivation.ConfigureAwait(false); + + //crash of secondary server + if (ddSecondaryTask.IsCompleted) + { + if (await SecondaryCrash().ConfigureAwait(false)) + return; + SecondaryRestart(); + } + //crash of primary server + else + { + if (await PrimaryCrash().ConfigureAwait(false)) + return; + PrimaryRestart(); + } + } while (true); + } + finally + { + secondaryCts?.Dispose(); + } + } + finally + { + primaryCts.Dispose(); + } + } while (true); + } + + /// + public async Task Start(ILaunchParametersFactory launchParametersFactory, CancellationToken cancellationToken) + { + TaskCompletionSource taskCompletionSource; + lock (this) + { + if (watchdogTask != null) + throw new InvalidOperationException("Watchdog already running!"); + watchdogCancellationTokenSource?.Dispose(); + watchdogCancellationTokenSource = new CancellationTokenSource(); + taskCompletionSource = new TaskCompletionSource(); + watchdogTask = Run(launchParametersFactory, taskCompletionSource, watchdogCancellationTokenSource.Token); + } + + using (cancellationToken.Register(() => watchdogCancellationTokenSource.Cancel())) + await taskCompletionSource.Task.ConfigureAwait(false); + } + + /// + public Task Stop() + { + lock(this) + { + if (watchdogTask == null) + throw new InvalidOperationException("Watchdog not running!"); + watchdogCancellationTokenSource.Cancel(); + var task = watchdogTask; + watchdogTask = null; + return task; + } + } + } +} From 4ac4f3f9870a71bb16d92ed535ff3440a09678ec Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 2 May 2018 16:41:10 -0400 Subject: [PATCH 126/397] Watchdog for life --- .../Components/DreamDaemonExecutor.cs | 10 +++++----- .../Components/IDreamDaemonExecutor.cs | 3 ++- .../Components/IInstanceManager.cs | 2 +- .../Components/IInstanceShutdownHandler.cs | 10 ++++++++++ .../Components/IInstanceShutdownMethod.cs | 11 ----------- .../Components/InstanceManager.cs | 10 +++++++--- src/Tgstation.Server.Host/Components/Watchdog.cs | 2 +- 7 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs delete mode 100644 src/Tgstation.Server.Host/Components/IInstanceShutdownMethod.cs diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index c56c2a6e4c..f8b74ce401 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -33,9 +33,9 @@ namespace Tgstation.Server.Host.Components } /// - /// The for the + /// The for the /// - readonly IInstanceShutdownMethod instanceShutdownMethod; + readonly IInstanceShutdownHandler instanceShutdownMethod; /// /// The for the /// @@ -46,14 +46,14 @@ namespace Tgstation.Server.Host.Components /// /// The value of /// The value of - public DreamDaemonExecutor(IInstanceShutdownMethod instanceShutdownMethod, IIOManager ioManager) + public DreamDaemonExecutor(IInstanceShutdownHandler instanceShutdownMethod, IIOManager ioManager) { this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); } /// - public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken) + public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, bool alwaysKill, CancellationToken cancellationToken) { using (var proc = new Process()) { @@ -85,7 +85,7 @@ namespace Tgstation.Server.Host.Components } finally { - if (!instanceShutdownMethod.GracefulShutdown) + if (!alwaysKill && !await instanceShutdownMethod.PreserveActiveExecutablesIfNecessary(launchParameters, accessToken, proc.Id, usePrimaryPort).ConfigureAwait(false)) { proc.Kill(); proc.WaitForExit(); diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index 5ed6534bbf..c6e444cecd 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -18,8 +18,9 @@ namespace Tgstation.Server.Host.Components /// The path to the .dmb to run, the working directory will be derived from this /// The access token to be used for communication /// If the server should open on or of + /// If the resulting process should never be left alive /// The for the operation /// A representing the lifetime of the process and resulting in the exit code - Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken); + Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, bool alwaysKill, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index 356a2fd9c8..080ab59d06 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing s /// - interface IInstanceManager : IInstanceShutdownMethod + interface IInstanceManager : IInstanceShutdownHandler { /// /// Get the associated with given diff --git a/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs b/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs new file mode 100644 index 0000000000..e07de394b6 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components +{ + interface IInstanceShutdownHandler + { + Task PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary); + } +} diff --git a/src/Tgstation.Server.Host/Components/IInstanceShutdownMethod.cs b/src/Tgstation.Server.Host/Components/IInstanceShutdownMethod.cs deleted file mode 100644 index 14ced74d67..0000000000 --- a/src/Tgstation.Server.Host/Components/IInstanceShutdownMethod.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Tgstation.Server.Host.Components -{ - interface IInstanceShutdownMethod - { - bool GracefulShutdown { get; set; } - } -} diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index ae48802658..1032aff8fa 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; @@ -14,9 +15,6 @@ namespace Tgstation.Server.Host.Components /// sealed class InstanceManager : IInstanceManager, IHostedService { - /// - public bool GracefulShutdown { get; set; } - /// /// The for the /// @@ -131,5 +129,11 @@ namespace Tgstation.Server.Host.Components await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); instances.Clear(); } + + /// + public Task PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary) + { + throw new NotImplementedException(); + } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index 3213641a92..71688ed6fe 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -78,7 +78,7 @@ namespace Tgstation.Server.Host.Components async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string accessToken, string dreamDaemonPath, bool isPrimary, CancellationToken cancellationToken) { using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) - return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, String.Concat(dmb.PrimaryDirectory, dmb.DmbName), accessToken, isPrimary, cancellationToken).ConfigureAwait(false); + return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, String.Concat(dmb.PrimaryDirectory, dmb.DmbName), accessToken, isPrimary, false, cancellationToken).ConfigureAwait(false); } /// From 39d084473b472f7868d184d536d6abc6cb555c5a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 3 May 2018 10:44:06 -0400 Subject: [PATCH 127/397] Refactor refactor refactor some more --- src/DMAPI/tgs/v4/api.dm | 6 +- src/DMAPI/tgs/v4/commands.dm | 2 +- src/Tgstation.Server.Api/Models/Instance.cs | 10 +++ .../Models/Internal/RevisionInformation.cs | 2 +- .../Models/Internal/TestMerge.cs | 6 ++ .../Components/DreamDaemonExecutor.cs | 44 ++++++++--- .../Components/DreamDaemonParameters.cs | 12 +-- src/Tgstation.Server.Host/Components/IChat.cs | 5 +- .../Components/IDmbProvider.cs | 7 +- .../Components/IDreamDaemonExecutor.cs | 8 +- .../Components/IInstance.cs | 2 + .../Components/IInstanceFactory.cs | 10 ++- .../Components/IInstanceManager.cs | 16 ++-- .../Components/Instance.cs | 2 + .../Components/InstanceFactory.cs | 2 +- .../Components/InstanceManager.cs | 8 +- .../Components/Models/InteropInfo.cs | 28 +++++++ .../Components/Models/TestMerge.cs | 16 ++++ .../Components/Watchdog.cs | 74 +++++++++++++++++-- src/Tgstation.Server.Host/Core/Application.cs | 10 +++ .../Models/DatabaseContext.cs | 2 +- src/Tgstation.Server.Host/Models/TestMerge.cs | 5 ++ 22 files changed, 229 insertions(+), 48 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Models/InteropInfo.cs create mode 100644 src/Tgstation.Server.Host/Components/Models/TestMerge.cs diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index e5e40bb9eb..e8ba604067 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -18,7 +18,7 @@ var/access_token var/instance_id var/instance_name - var/host_port + var/host_path var/port_1 var/port_2 var/cached_json @@ -50,7 +50,7 @@ access_token = cached_json["access_token"] instance_id = text2num(cached_json["instance_id"]) - host_port = text2num(cached_json["host_port"]) + host_path = cached_json["host_path"] if(cached_json["api_validate_only"]) Export(TGS4_COMM_VALIDATE) del(world) @@ -110,7 +110,7 @@ world.OpenPort(new_port) /datum/tgs_api/v4/proc/Export(command) - return world.Export("http://127.0.0.1:[host_port]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]") + return world.Export("[host_path]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]") /datum/tgs_api/v4/OnReboot() var/json = Export(TGS4_COMM_SERVER_REBOOT) diff --git a/src/DMAPI/tgs/v4/commands.dm b/src/DMAPI/tgs/v4/commands.dm index bf32f360e3..0d4e72f0fc 100644 --- a/src/DMAPI/tgs/v4/commands.dm +++ b/src/DMAPI/tgs/v4/commands.dm @@ -15,7 +15,7 @@ results[command_name] = list("help_text" = stc.help_text, "admin_only" = stc.admin_only) custom_commands[command_name] = stc - var/commands_file = cached_json["chat_commands_file"] + var/commands_file = cached_json["chat_commands_json"] if(!commands_file) return text2file(json_encode(results), commands_file) diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index bde9f13522..22bd31e1be 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -40,5 +40,15 @@ namespace Tgstation.Server.Api.Models /// [Permissions(WriteRight = InstanceManagerRights.SetConfiguration)] public bool ConfigurationAllowed { get; set; } + + /// + public Instance CloneMetadata() => new Instance + { + Id = Id, + Name = Name, + Path = Path, + Online = Online, + ConfigurationAllowed = ConfigurationAllowed + }; } } diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs index 44eb907a89..a61788167f 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Api.Models.Internal /// [Permissions(DenyWrite = true)] [Required, StringLength(40)] - public string Revision { get; set; } + public string Commit { get; set; } /// /// The sha of the most recent remote commit diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs b/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs index 9e1d04f64d..fd6052390a 100644 --- a/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs +++ b/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs @@ -31,6 +31,12 @@ namespace Tgstation.Server.Api.Models.Internal [Required] public string BodyAtMerge { get; set; } + /// + /// The URL of the pull request + /// + [Required] + public Uri Url { get; set; } + /// /// The author of the pull request /// diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index f8b74ce401..0f3a47b026 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -1,10 +1,13 @@ -using System; +using Newtonsoft.Json; +using System; using System.Diagnostics; using System.Globalization; +using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Models; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components @@ -53,18 +56,41 @@ namespace Tgstation.Server.Host.Components } /// - public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, bool alwaysKill, CancellationToken cancellationToken) + public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken) { + if (launchParameters == null) + throw new ArgumentNullException(nameof(launchParameters)); + if (dreamDaemonPath == null) + throw new ArgumentNullException(nameof(dreamDaemonPath)); + if (dmbProvider == null) + throw new ArgumentNullException(nameof(dmbProvider)); + if (interopInfo == null) + throw new ArgumentNullException(nameof(interopInfo)); + + if (interopInfo.NextPort != launchParameters.PrimaryPort && interopInfo.NextPort != launchParameters.SecondaryPort) + throw new ArgumentOutOfRangeException(nameof(interopInfo), interopInfo, "interopInfo.NextPort must match primary or secondary ports!"); + + var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; + + //serialize the interop info to the json + var jsonPath = String.Concat(Guid.NewGuid(), ".tgs.json"); + + var json = JsonConvert.SerializeObject(interopInfo); + + using (var proc = new Process()) { proc.StartInfo.FileName = dreamDaemonPath; - proc.StartInfo.WorkingDirectory = ioManager.GetDirectoryName(dmbPath); - proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}&{8}={9}&{10}={11}&{12}={13}\"", dmbPath, usePrimaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel), - DreamDaemonParameters.AccessToken, accessToken, + proc.StartInfo.WorkingDirectory = isPrimary ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory; + await ioManager.WriteAllBytes(ioManager.ConcatPath(proc.StartInfo.WorkingDirectory, jsonPath), Encoding.UTF8.GetBytes(json), cancellationToken).ConfigureAwait(false); + + proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", + dmbProvider.DmbName, + isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, + launchParameters.AllowWebClient ? "-webclient " : String.Empty, + SecurityWord(launchParameters.SecurityLevel), DreamDaemonParameters.HostVersion, Application.Version, - DreamDaemonParameters.IsSecondaryServer, !usePrimaryPort, - DreamDaemonParameters.PrimaryPort, launchParameters.PrimaryPort, - DreamDaemonParameters.SecondaryPort, launchParameters.SecondaryPort); + DreamDaemonParameters.InfoJsonPath, jsonPath); proc.EnableRaisingEvents = true; var tcs = new TaskCompletionSource(); @@ -85,7 +111,7 @@ namespace Tgstation.Server.Host.Components } finally { - if (!alwaysKill && !await instanceShutdownMethod.PreserveActiveExecutablesIfNecessary(launchParameters, accessToken, proc.Id, usePrimaryPort).ConfigureAwait(false)) + if (!alwaysKill && !await instanceShutdownMethod.PreserveActiveExecutablesIfNecessary(launchParameters, interopInfo.AccessToken, proc.Id, isPrimary).ConfigureAwait(false)) { proc.Kill(); proc.WaitForExit(); diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs index e5138d54da..4c7088c69e 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs @@ -3,12 +3,12 @@ static class DreamDaemonParameters { /// - /// Do not change to keep forwards/backwards api compatibility + /// Host version. Do not change to keep forwards/backwards api compatibility /// - public const string HostVersion = "server_service_versions"; - public const string AccessToken = "tgs_key"; - public const string PrimaryPort = "tgs_port1"; - public const string SecondaryPort = "tgs_port2"; - public const string IsSecondaryServer = "tgs_second"; + public const string HostVersion = "server_service_version"; + /// + /// Path to json + /// + public const string InfoJsonPath = "tgs_json"; } } diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/IChat.cs index 9ebd0877c2..bd43137bca 100644 --- a/src/Tgstation.Server.Host/Components/IChat.cs +++ b/src/Tgstation.Server.Host/Components/IChat.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Hosting; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -41,9 +42,11 @@ namespace Tgstation.Server.Host.Components /// Send a chat to a given set of /// /// The message being sent - /// The s of the s to send to + /// The s of the s to send to /// The for the operation /// A representing the running operation Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken); + + Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs index b0d190ab34..4825fb2914 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -1,5 +1,5 @@ using System; -using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { @@ -22,5 +22,10 @@ namespace Tgstation.Server.Host.Components /// The secondary game directory with a trailing directory separator /// string SecondaryDirectory { get; } + + /// + /// The of the .dmb + /// + RevisionInformation RevisionInformation { get; } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index c6e444cecd..2f7cbc150d 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Models; namespace Tgstation.Server.Host.Components { @@ -15,12 +16,11 @@ namespace Tgstation.Server.Host.Components /// The /// The that is completed when dream daemon starts without crashing /// The path to the dreamdaemon executable - /// The path to the .dmb to run, the working directory will be derived from this - /// The access token to be used for communication - /// If the server should open on or of + /// The for the .dmb to run + /// The for the run /// If the resulting process should never be left alive /// The for the operation /// A representing the lifetime of the process and resulting in the exit code - Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, bool alwaysKill, CancellationToken cancellationToken); + Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index d28a32d9a6..3735a5cc13 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -16,6 +16,8 @@ namespace Tgstation.Server.Host.Components IConfiguration Configuration { get; } + Api.Models.Instance GetMetadata(); + void Rename(string newName); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index a31d69f2f4..21d905f7a4 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -2,8 +2,16 @@ namespace Tgstation.Server.Host.Components { + /// + /// Factory for creating s + /// interface IInstanceFactory { - IInstance CreateInstance(Models.Instance metadata); + /// + /// Create an + /// + /// The + /// A new + IInstance CreateInstance(Host.Models.Instance metadata); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index 080ab59d06..34c5f6123c 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -11,33 +11,33 @@ namespace Tgstation.Server.Host.Components /// /// Get the associated with given /// - /// The of the desired + /// The of the desired /// The associated with the given - IInstance GetInstance(Models.Instance metadata); + IInstance GetInstance(Host.Models.Instance metadata); /// /// Online an /// - /// The of the desired + /// The of the desired /// The for the operation /// A representing the running operation - Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken); + Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken); /// /// Offline an /// - /// The of the desired + /// The of the desired /// The for the operation /// A representing the running operation - Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken); + Task OfflineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken); /// /// Move an /// - /// The of the desired + /// The of the desired /// The new path of the . will have this set on if the operation completes successfully /// The for the operation /// A representing the running operation - Task MoveInstance(Models.Instance metadata, string newPath, CancellationToken cancellationToken); + Task MoveInstance(Host.Models.Instance metadata, string newPath, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index e31e5177c6..ce439ee988 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -31,6 +31,8 @@ namespace Tgstation.Server.Host.Components Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); } + public Api.Models.Instance GetMetadata() => metadata.CloneMetadata(); + public void Rename(string newName) { if (String.IsNullOrWhiteSpace(newName)) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index d003ad7051..d241d749de 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); /// - public IInstance CreateInstance(Models.Instance metadata) + public IInstance CreateInstance(Host.Models.Instance metadata) { //Create the ioManager for the instance diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 1032aff8fa..59f4ee867c 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.Components } /// - public IInstance GetInstance(Models.Instance metadata) + public IInstance GetInstance(Host.Models.Instance metadata) { lock (this) { @@ -58,7 +58,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken) + public async Task MoveInstance(Host.Models.Instance instance, string newPath, CancellationToken cancellationToken) { if (newPath == null) throw new ArgumentNullException(nameof(newPath)); @@ -84,7 +84,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken) + public async Task OfflineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) { IInstance instance; lock (this) @@ -97,7 +97,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken) + public async Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) { var instance = instanceFactory.CreateInstance(metadata); lock (this) diff --git a/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs b/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs new file mode 100644 index 0000000000..1376421631 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components.Models +{ + sealed class InteropInfo + { + public string AccessToken { get; set; } + + public long InstanceId { get; set; } + + public string HostPath { get; set; } + + public bool ApiValidateOnly { get; set; } + + public string InstanceName { get; set; } + + public ushort NextPort { get; set; } + + public string ChatChannelsJson { get; set; } + + public string ChatCommandsJson { get; set; } + + public RevisionInformation Revision { get; set; } + + public List TestMerges { get; } = new List(); + } +} diff --git a/src/Tgstation.Server.Host/Components/Models/TestMerge.cs b/src/Tgstation.Server.Host/Components/Models/TestMerge.cs new file mode 100644 index 0000000000..1996ad5bc3 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Models/TestMerge.cs @@ -0,0 +1,16 @@ +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components.Models +{ + sealed class TestMerge : RevisionInformation + { + public int Number { get; set; } + public string Title { get; set; } + public string Body { get; set; } + public string Author { get; set; } + public string Url { get; set; } + public string PullRequestCommit { get; set; } + public long TimeMerged { get; set; } + public string Comment { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index 71688ed6fe..f7eaf3d327 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -2,6 +2,8 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Models; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components @@ -14,6 +16,10 @@ namespace Tgstation.Server.Host.Components /// readonly IByond byond; /// + /// The for the + /// + readonly IChat chat; + /// /// The for the /// readonly IDreamDaemonExecutor dreamDaemonExecutor; @@ -33,6 +39,14 @@ namespace Tgstation.Server.Host.Components /// The for the /// readonly IEventConsumer eventConsumer; + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + /// + /// The of the + /// + readonly long instanceId; /// /// Represents the currently running @@ -47,19 +61,26 @@ namespace Tgstation.Server.Host.Components /// Construct a /// /// The value of + /// The value of /// The value of /// The value of /// The value of /// The value of /// The value of - public Watchdog(IByond byond, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, ICryptographySuite cryptographySuite, IEventConsumer eventConsumer) + /// The value of + /// The value of + public Watchdog(IByond byond, IChat chat, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, ICryptographySuite cryptographySuite, IEventConsumer eventConsumer, IInstanceManager instanceManager, long instanceId) { this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); + this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + + this.instanceId = instanceId; } /// @@ -70,19 +91,47 @@ namespace Tgstation.Server.Host.Components /// /// The for the run /// The to be completed once the server starts if any - /// The access token for the server + /// The the server /// The path to the DreamDaemon executable - /// If a primary server is being launched /// The for the operation /// A resulting in the exit code of DreamDaemon - async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string accessToken, string dreamDaemonPath, bool isPrimary, CancellationToken cancellationToken) + async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, string dreamDaemonPath, CancellationToken cancellationToken) { using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) - return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, String.Concat(dmb.PrimaryDirectory, dmb.DmbName), accessToken, isPrimary, false, cancellationToken).ConfigureAwait(false); + { + var chatJsonGuid = Guid.NewGuid(); + var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; + + //set up chat stuff + interopInfo.ChatChannelsJson = String.Concat(chatJsonGuid, ".channels.json"); + //only track chat commands from primary server + if(isPrimary) + interopInfo.ChatCommandsJson = String.Concat(chatJsonGuid, ".commands.json"); + + //set up revision + interopInfo.Revision = dmb.RevisionInformation; + foreach (var I in dmb.RevisionInformation.TestMerges) + interopInfo.TestMerges.Add(new Models.TestMerge + { + Author = I.Author, + Body = I.BodyAtMerge, + Comment = I.Comment, + Commit = I.RevisionInformation.Commit, + OriginRevision = I.RevisionInformation.OriginRevision, + Number = I.Number, + PullRequestCommit = I.PullRequestRevision, + TimeMerged = I.MergedAt.ToUnixTimeSeconds(), + Title = I.TitleAtMerge, + Url = I.Url.ToString() + }); + + using (await chat.TrackJsons(isPrimary ? dmb.PrimaryDirectory : dmb.SecondaryDirectory, interopInfo.ChatChannelsJson, isPrimary ? interopInfo.ChatCommandsJson : null, cancellationToken).ConfigureAwait(false)) + return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, cancellationToken).ConfigureAwait(false); + } } /// - /// Locks in a version and runs a server through + /// Locks in a version and runs a server through /// /// The for the run /// The to be completed once the server starts if any @@ -97,7 +146,18 @@ namespace Tgstation.Server.Host.Components try { var ddToken = cancellationTokenSource.Token; - var ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, onSuccessfulStartup, accessToken, dreamDaemonPath, isPrimary, ddToken), false, true); + + var interopInfo = new InteropInfo + { + AccessToken = accessToken, + ApiValidateOnly = false, + HostPath = Application.HostingPath, + InstanceId = instanceId, + NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, + InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, + }; + + var ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false, true); interop.SetRun(isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken, isPrimary); return ddTask; } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index cd65f38a98..095e08b516 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -10,6 +11,7 @@ using Microsoft.IdentityModel.Tokens; using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; +using System.Linq; using System.Reflection; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Configuration; @@ -24,6 +26,11 @@ namespace Tgstation.Server.Host.Core /// sealed class Application { + /// + /// The url the server can be reached at locally + /// + public static string HostingPath { get; private set; } + /// /// The version of the /// @@ -151,6 +158,9 @@ namespace Tgstation.Server.Host.Core if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); + var addresses = applicationBuilder.ServerFeatures.Get(); + HostingPath = addresses.Addresses.First(); + if (hostingEnvironment.IsDevelopment()) applicationBuilder.UseDeveloperExceptionPage(); diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 4dc62319ba..5f0ffef80f 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -109,7 +109,7 @@ namespace Tgstation.Server.Host.Models LogModelBuilderHelper.Build(modelBuilder.Entity()); modelBuilder.Entity().ToTable(nameof(Logs)); - modelBuilder.Entity().HasIndex(x => x.Revision).IsUnique(); + modelBuilder.Entity().HasIndex(x => x.Commit).IsUnique(); var user = modelBuilder.Entity(); user.HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique(); user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict); diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index e72fc66e3c..05e201c04b 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -10,5 +10,10 @@ namespace Tgstation.Server.Host.Models /// [Required] public User MergedBy { get; set; } + + /// + /// The for the + /// + public RevisionInformation RevisionInformation { get; set; } } } From dff5f975e6227d508a59e5f8102ada7b02f43cf8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 11:16:39 -0400 Subject: [PATCH 128/397] DreamMaker is done --- src/DMAPI/tgs/v4/api.dm | 36 ++- .../Models/Internal/CompileJob.cs | 8 +- .../Components/DreamDaemon.cs | 19 +- .../Components/DreamMaker.cs | 276 ++++++++++++++++++ .../Components/IByond.cs | 5 +- .../Components/IDmbFactory.cs | 1 + .../Components/IDreamMaker.cs | 8 +- .../Components/IInterop.cs | 14 +- .../Components/IInteropControl.cs | 29 ++ .../Components/IRepository.cs | 1 + .../Components/Instance.cs | 4 +- .../Components/InstanceManager.cs | 10 + .../Components/Models/TestMerge.cs | 3 + .../Components/ServerControlEventType.cs | 6 +- .../Components/TemporaryDmbProvider.cs | 37 +++ .../Components/Watchdog.cs | 235 ++++++++------- 16 files changed, 528 insertions(+), 164 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/DreamMaker.cs create mode 100644 src/Tgstation.Server.Host/Components/IInteropControl.cs create mode 100644 src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index e8ba604067..981a30e1ad 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -2,11 +2,11 @@ #define TGS4_TOPIC_COMMAND "tgs_com" #define TGS4_TOPIC_TOKEN "tgs_tok" -#define TGS4_TOPIC_SUCCESS "tgs_success" #define TGS4_TOPIC_SWAP "tgs_swap" #define TGS4_TOPIC_SWAP_DELAYED "tgs_swap_delayed" #define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm" #define TGS4_TOPIC_EVENT "tgs_event" +#define TGS4_TOPIC_IDENTIFY "tgs_ident" #define TGS4_COMM_VALIDATE "tgs_vali" #define TGS4_COMM_SERVER_PRIMED "tgs_prime" @@ -23,6 +23,8 @@ var/port_2 var/cached_json var/json_path + + var/list/intercepted_message_queue var/list/custom_commands @@ -95,8 +97,14 @@ return json_encode(list("error" = "Error running chat command!")) return result if(TGS4_TOPIC_EVENT) + intercepted_message_queue = list() event_handler.HandleEvent(text2num(params[TGS4_TOPIC_EVENT])) - return TGS4_TOPIC_SUCCESS + . = json_encode(intercepted_message_queue) + intercepted_message_queue = null + return + if(TGS4_TOPIC_IDENTIFY) + //they want to know our initial port + return "[port_1]" return "Unknown command: [command]" @@ -104,13 +112,16 @@ set waitfor = FALSE var/new_port = world.port == port_1 ? port_2 : port_1 event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) - world.OpenPort("none") //close the port if(delayed) + world.OpenPort("none") //close the port sleep(50) //wait for other server to close port - world.OpenPort(new_port) + + //do NOT give up, if we remain unresponsive we will be killed + while(!world.OpenPort(new_port)) + sleep(10) /datum/tgs_api/v4/proc/Export(command) - return world.Export("[host_path]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]") + return file2text(world.Export("[host_path]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]")["CONTENT"]) /datum/tgs_api/v4/OnReboot() var/json = Export(TGS4_COMM_SERVER_REBOOT) @@ -163,15 +174,24 @@ var/datum/tgs_chat_channel/channel = I ids += channel.id message = list("message" = message, "channels" = ids) - Export("[TGS4_COMM_CHAT] [json_encode(message)]") + if(interepted_message_queue) + interepted_message_queue += list(message) + else + Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) message = list("message" = message, "channels" = admin_only ? "admin" : "game") - Export("[TGS4_COMM_CHAT] [json_encode(message)]") + if(interepted_message_queue) + interepted_message_queue += list(message) + else + Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) message = list("message" = message, "user" = list("id" = user.id, "channel" = user.channel.id)) - Export("[TGS4_COMM_CHAT] [json_encode(message)]") + if(interepted_message_queue) + interepted_message_queue += list(message) + else + Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatChannelInfo() . = list() diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index d31366048f..0833d79e34 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -26,14 +26,14 @@ namespace Tgstation.Server.Api.Models.Internal public DateTimeOffset FinishedAt { get; set; } /// - /// The detected DMAPI version + /// If the DMAPI version detected is compatible /// - public Version DMApiVersion { get; set; } + public bool DMApiValidated { get; set; } /// /// The .dme file used for compilation /// - public string DmePath { get; set; } + public string DmeName { get; set; } /// /// Textual output of DM @@ -43,7 +43,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// The Game folder the results were compiled into /// - public Guid? OutputGuid { get; set; } + public Guid? DirectoryName { get; set; } /// /// Exit code of DM. If diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index 04055e8757..1d6846ccb3 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -68,9 +68,7 @@ namespace Tgstation.Server.Host.Components this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); - - interop.SetServerControlHandler(OnServerControl); - + autoStart = initialSettings.AutoStart; semaphore = new SemaphoreSlim(1); @@ -79,21 +77,6 @@ namespace Tgstation.Server.Host.Components /// public void Dispose() => semaphore.Dispose(); - /// - /// Handler for server control events - /// - /// The - /// The for the operation - /// A representing the running operation - static async Task OnServerControl(ServerControlEvent serverControlEventArgs, CancellationToken cancellationToken) - { - if (serverControlEventArgs == null) - throw new ArgumentNullException(nameof(serverControlEventArgs)); - - await Task.Yield(); - throw new NotImplementedException(); - } - /// public async Task CancelGracefulActions(CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs new file mode 100644 index 0000000000..4abd5e10b5 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Models; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class DreamMaker : IDreamMaker + { + /// + /// Name of the primary directory used for compilation + /// + const string ADirectoryName = "A"; + + /// + /// The for + /// + readonly IRepositoryManager repositoryManager; + /// + /// The for + /// + readonly IIOManager ioManager; + /// + /// The for + /// + readonly IConfiguration configuration; + /// + /// The for + /// + readonly IDreamDaemonExecutor dreamDaemonExecutor; + /// + /// The for + /// + readonly IByond byond; + /// + /// The for + /// + readonly IInterop interop; + /// + /// The for + /// + readonly ICryptographySuite cryptographySuite; + + /// + /// Construct + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + public DreamMaker(IRepositoryManager repositoryManager, IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICryptographySuite cryptographySuite) + { + this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); + this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); + this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + } + + /// + /// Run a quick DD instance to test the DMAPI is installed on the target code + /// + /// The path to the DreamDaemon executable + /// The for the operation + /// The for the operation + /// A resulting in if the DMAPI was successfully validated, otherwise + async Task VerifyApi(string dreamDaemonPath, Host.Models.CompileJob job, CancellationToken cancellationToken) + { + var launchParameters = new DreamDaemonLaunchParameters + { + AllowWebClient = false, + PrimaryPort = 0, //pick any port + SecurityLevel = DreamDaemonSecurity.Safe //all it needs to read the file and exit + }; + + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { + var interopInfo = new InteropInfo + { + ApiValidateOnly = true, + HostPath = Application.HostingPath, + AccessToken = cryptographySuite.GetSecureString() + }; + + using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null)) + { + var ddTcs = new TaskCompletionSource(); + control.OnServerControl += (sender, e) => { + if (e.EventType == ServerControlEventType.ServerUnresponsive) + ddTcs.SetResult(null); + }; + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, ".dmb")))), interopInfo, true, cts.Token); + + await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); + + if (!ddTestTask.IsCompleted) + cts.Cancel(); + + return await ddTestTask.ConfigureAwait(false) == 0 && !ddTcs.Task.IsCompleted; + } + } + } + + /// + /// Compiles a .dme with DreamMaker + /// + /// The path to the DreamMaker executable + /// The for the operation + /// The for the operation + /// A representing the running operation + async Task RunDreamMaker(string dreamMakerPath, Host.Models.CompileJob job, CancellationToken cancellationToken) + { + using (var dm = new Process()) + { + dm.StartInfo.FileName = dreamMakerPath; + dm.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "-clean {0}.dme", job.DmeName); + dm.StartInfo.WorkingDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)); + dm.StartInfo.RedirectStandardOutput = true; + dm.StartInfo.RedirectStandardError = true; + var OutputList = new StringBuilder(); + var eventHandler = new DataReceivedEventHandler( + delegate (object sender, DataReceivedEventArgs e) + { + OutputList.Append(Environment.NewLine); + OutputList.Append(e.Data); + } + ); + dm.OutputDataReceived += eventHandler; + dm.ErrorDataReceived += eventHandler; + + dm.EnableRaisingEvents = true; + var dmTcs = new TaskCompletionSource(); + dm.Exited += (a, b) => dmTcs.SetResult(null); + + dm.Start(); + try + { + using (cancellationToken.Register(() => dmTcs.SetCanceled())) + await dmTcs.Task.ConfigureAwait(false); + } + finally + { + if (!dm.HasExited) + { + dm.Kill(); + dm.WaitForExit(); + } + } + + job.Output = OutputList.ToString(); + job.ExitCode = dm.ExitCode; + } + } + + async Task ModifyDme(Host.Models.CompileJob job, CancellationToken cancellationToken) + { + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var dmePath = ioManager.ConcatPath(dirA, String.Concat(job.DmeName, ".dme")); + var dmeReadTask = ioManager.ReadAllBytes(dmePath, cancellationToken); + + var dmeModificationsTask = configuration.CopyDMFilesTo(ioManager.ResolvePath(dirA), cancellationToken); + + var dmeBytes = await dmeReadTask.ConfigureAwait(false); + var dme = Encoding.UTF8.GetString(dmeBytes); + + var dmeModifications = await dmeModificationsTask.ConfigureAwait(false); + + if (!dmeModifications.Any()) + return; + + var dmeLines = new List(dme.Split(new[] { Environment.NewLine }, StringSplitOptions.None)); + for (var I = 0; I < dmeLines.Count; ++I) + { + var line = dmeLines[I]; + if (line.Contains("BEGIN_INCLUDE")) + { + dmeLines.InsertRange(I + 1, dmeModifications); + break; + } + } + + dmeBytes = Encoding.UTF8.GetBytes(String.Join(Environment.NewLine, dmeLines)); + await ioManager.WriteAllBytes(dmePath, dmeBytes, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task Compile(string dmeName, CancellationToken cancellationToken) + { + var job = new Host.Models.CompileJob + { + DirectoryName = Guid.NewGuid(), + StartedAt = DateTimeOffset.Now, + DmeName = dmeName + }; + + await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B"); + + async Task CleanupFailedCompile() + { + try + { + await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); + } + catch { } + }; + + try + { + //copy the repository + var fullDirA = ioManager.ResolvePath(dirA); + using (var repository = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + job.RevisionInformation = new Host.Models.RevisionInformation + { + Commit = repository.Head + }; + await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); + } + + await ModifyDme(job, cancellationToken).ConfigureAwait(false); + + //run compiler, verify api + var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + { + await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + + return await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); + }, true).ConfigureAwait(false); + + if(!ddVerified) + { + //server never validated + job.FinishedAt = DateTimeOffset.Now; + await CleanupFailedCompile().ConfigureAwait(false); + return job; + } + + job.DMApiValidated = true; + + //duplicate the dmb et al + await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); + + //symlink in the static data + var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); + await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); + await symATask.ConfigureAwait(false); + + job.FinishedAt = DateTimeOffset.Now; + return job; + } + catch + { + await CleanupFailedCompile().ConfigureAwait(false); + throw; + } + } + } +} diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByond.cs index b49cdc826b..acf4f4c7cc 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByond.cs @@ -27,11 +27,10 @@ namespace Tgstation.Server.Host.Components /// Lock the current installation's location and run an /// /// The return type of - /// A taking the path to either dm.exe or dreamdaemon.exe and returning a + /// A taking the path to either dm.exe and dreamdaemon.exe and returning a /// Use the staged installation if possible - /// Pass the path of dreamdaemon.exe to if dm.exe otherwise /// A resulting in the return type of - Task UseExecutable(Func> operation, bool stagedIfExists, bool dreamDaemon); + Task UseExecutables(Func> operation, bool stagedIfExists); /// /// Clears the cache folder diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/IDmbFactory.cs index 2ed1a771ff..19ca0dc1ae 100644 --- a/src/Tgstation.Server.Host/Components/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/IDmbFactory.cs @@ -1,5 +1,6 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 9f119ac27d..8e6036dc55 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -8,14 +8,14 @@ namespace Tgstation.Server.Host.Components /// /// For managing the compiler /// - interface IDreamMaker : IHostedService + interface IDreamMaker { /// /// Starts a compile /// - /// The .dme file to use + /// The .dme file to use without the extension /// The for the operation - /// A resulting in the partially populated for the operation - Task Compile(string dmePath, CancellationToken cancellationToken); + /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated + Task Compile(string dmeName, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs index 32edd2e047..ff5337dc30 100644 --- a/src/Tgstation.Server.Host/Components/IInterop.cs +++ b/src/Tgstation.Server.Host/Components/IInterop.cs @@ -9,18 +9,8 @@ namespace Tgstation.Server.Host.Components /// interface IInterop { - bool SecondaryIsOther { get; set; } + IInteropControl ReconnectToRun(string primaryAccessToken, string secondaryAccessToken, ushort primaryPort, ushort secondaryPort); - void SetServerControlHandler(Func serverControlHandler); - void SetChatMessageHandler(Func chatMessageHandler); - - Task GetApiVersion(CancellationToken cancellationToken); - - void SetRun(ushort? port, string accessToken, bool primary); - - Task ActivateOtherServer(CancellationToken cancellationToken); - - Task ChatCommand(string command, string arguments, CancellationToken cancellationToken); - void OnServerPrimed(Action actionToTake); + IInteropControl CreateRun(ushort primaryPort, ushort? secondaryPort, Func chatMessageHandler); } } diff --git a/src/Tgstation.Server.Host/Components/IInteropControl.cs b/src/Tgstation.Server.Host/Components/IInteropControl.cs new file mode 100644 index 0000000000..2fc3f5ea6d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInteropControl.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents a control set for one or two DreamDaemon instances + /// + interface IInteropControl : IDisposable + { + /// + /// When a server control message arrives + /// + event EventHandler OnServerControl; + + bool TwinServerMode { get; } + + bool SecondaryIsOther { get; } + + string PrimaryAccessToken { get; } + + string SecondaryAccessToken { get; } + + Task ActivateOtherServer(CancellationToken cancellationToken); + + Task ChangePort(ushort newPort, bool forPrimary, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/IRepository.cs index e52261b3dc..7acd5053df 100644 --- a/src/Tgstation.Server.Host/Components/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/IRepository.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index ce439ee988..454b1063de 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -40,8 +40,8 @@ namespace Tgstation.Server.Host.Components metadata.Name = newName; } - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamMaker.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken)); + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken)); - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamMaker.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken)); } } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 59f4ee867c..ed93f70fa9 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -49,6 +49,8 @@ namespace Tgstation.Server.Host.Components /// public IInstance GetInstance(Host.Models.Instance metadata) { + if (metadata == null) + throw new ArgumentNullException(nameof(metadata)); lock (this) { if (!instances.TryGetValue(metadata.Id, out IInstance instance)) @@ -86,6 +88,8 @@ namespace Tgstation.Server.Host.Components /// public async Task OfflineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) { + if (metadata == null) + throw new ArgumentNullException(nameof(metadata)); IInstance instance; lock (this) { @@ -99,6 +103,8 @@ namespace Tgstation.Server.Host.Components /// public async Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) { + if (metadata == null) + throw new ArgumentNullException(nameof(metadata)); var instance = instanceFactory.CreateInstance(metadata); lock (this) { @@ -133,6 +139,10 @@ namespace Tgstation.Server.Host.Components /// public Task PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary) { + if (launchParameters == null) + throw new ArgumentNullException(nameof(launchParameters)); + if (accessToken == null) + throw new ArgumentNullException(nameof(accessToken)); throw new NotImplementedException(); } } diff --git a/src/Tgstation.Server.Host/Components/Models/TestMerge.cs b/src/Tgstation.Server.Host/Components/Models/TestMerge.cs index 1996ad5bc3..cda4aaa5ea 100644 --- a/src/Tgstation.Server.Host/Components/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Components/Models/TestMerge.cs @@ -2,6 +2,9 @@ namespace Tgstation.Server.Host.Components.Models { + /// + /// This model mirrors /datum/tgs_revision_information/test_merge + /// sealed class TestMerge : RevisionInformation { public int Number { get; set; } diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventType.cs b/src/Tgstation.Server.Host/Components/ServerControlEventType.cs index 11a9e23bac..9468bcabaa 100644 --- a/src/Tgstation.Server.Host/Components/ServerControlEventType.cs +++ b/src/Tgstation.Server.Host/Components/ServerControlEventType.cs @@ -16,6 +16,10 @@ /// /// The server requested that it's process be terminated /// - RequestedProcessTermination + RequestedProcessTermination, + /// + /// The server has stopped responding to heartbeats + /// + ServerUnresponsive, } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs new file mode 100644 index 0000000000..389256d050 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs @@ -0,0 +1,37 @@ +using System; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Temporary + /// + sealed class TemporaryDmbProvider : IDmbProvider + { + /// + public string DmbName { get; } + + /// + public string PrimaryDirectory { get; } + + /// + public string SecondaryDirectory => throw new NotSupportedException(); + + /// + public RevisionInformation RevisionInformation => null; + + /// + /// Construct a + /// + /// The value of + /// The value of + public TemporaryDmbProvider(string directory, string dmb) + { + DmbName = dmb ?? throw new ArgumentNullException(nameof(dmb)); + PrimaryDirectory = directory ?? throw new ArgumentNullException(nameof(directory)); + } + + /// + public void Dispose() { } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index f7eaf3d327..a98ce38c27 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Models; using Tgstation.Server.Host.Core; -using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components { @@ -32,10 +31,6 @@ namespace Tgstation.Server.Host.Components /// readonly IDmbFactory dmbFactory; /// - /// The for the - /// - readonly ICryptographySuite cryptographySuite; - /// /// The for the /// readonly IEventConsumer eventConsumer; @@ -65,18 +60,16 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - /// The value of /// The value of /// The value of /// The value of - public Watchdog(IByond byond, IChat chat, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, ICryptographySuite cryptographySuite, IEventConsumer eventConsumer, IInstanceManager instanceManager, long instanceId) + public Watchdog(IByond byond, IChat chat, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInstanceManager instanceManager, long instanceId) { this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); - this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); @@ -154,12 +147,11 @@ namespace Tgstation.Server.Host.Components HostPath = Application.HostingPath, InstanceId = instanceId, NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, + //this line feels hacky, change it and remove the instanceManager dep? InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, }; - - var ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false, true); - interop.SetRun(isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken, isPrimary); - return ddTask; + + return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false); } catch { @@ -191,6 +183,19 @@ namespace Tgstation.Server.Host.Components return false; } + /// + /// Handle + /// + /// The + /// The for the operation + /// A representing the running operation + Task HandleChatMessage(ChatMessageEventArgs e, CancellationToken cancellationToken) + { + if (e == null) + throw new ArgumentNullException(nameof(e)); + return chat.SendMessage(e.ChatResponse.Message, e.ChatResponse.ChannelIds, cancellationToken); + } + /// /// Main loop /// @@ -203,134 +208,140 @@ namespace Tgstation.Server.Host.Components if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null) throw new InvalidOperationException("No byond version installed!"); await byond.ClearCache(cancellationToken).ConfigureAwait(false); - var accessToken = cryptographySuite.GetSecureString(); + var initialLaunchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); var retries = 0; do { var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); - //load the event tcs' and get the initial launch parameters - var primaryPrimedTcs = new TaskCompletionSource(); - interop.OnServerPrimed(() => primaryPrimedTcs.SetResult(null)); - var initialLaunchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); - //start the primary server - var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, accessToken, true, cancellationToken, out CancellationTokenSource primaryCts); - try + using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort, initialLaunchParameters.SecondaryPort, HandleChatMessage)) { - //wait to make sure we got this far - await onSuccessfulStartup.Task.ConfigureAwait(false); - onSuccessfulStartup = null; - - //wait for either the server to exit or be primed - await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false); - - if (ddPrimaryTask.IsCompleted) + var primaryPrimedTcs = new TaskCompletionSource(); + control.OnServerControl += (sender, e) => { - if (await HandleServerCrashed(ddPrimaryTask, true, cancellationToken).ConfigureAwait(false)) - return; - ++retries; - continue; - } + if (e.FromPrimaryServer && e.EventType == ServerControlEventType.ServerPrimed) + primaryPrimedTcs.TrySetResult(null); + }; - var launchParameters = initialLaunchParameters; - Task ddSecondaryTask = null; - CancellationTokenSource secondaryCts = null; + //start the primary server + var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control.PrimaryAccessToken, true, cancellationToken, out CancellationTokenSource primaryCts); try { - do + //wait to make sure we got this far + await onSuccessfulStartup.Task.ConfigureAwait(false); + onSuccessfulStartup = null; + + //wait for either the server to exit or be primed + await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false); + + if (ddPrimaryTask.IsCompleted) { - if (ddSecondaryTask == null) - //start the secondary server - ddSecondaryTask = StartServer(initialLaunchParameters, null, accessToken, false, cancellationToken, out secondaryCts); + if (await HandleServerCrashed(ddPrimaryTask, true, cancellationToken).ConfigureAwait(false)) + return; + ++retries; + continue; + } - var newDmbTask = dmbFactory.OnNewerDmb(); - - //now we wait for something to happen - await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, newDmbTask).ConfigureAwait(false); - - //some helpers - void PrimaryRestart() + var launchParameters = initialLaunchParameters; + Task ddSecondaryTask = null; + CancellationTokenSource secondaryCts = null; + try + { + do { - primaryCts.Dispose(); - ddPrimaryTask = StartServer(initialLaunchParameters, null, accessToken, true, cancellationToken, out primaryCts); - } - void SecondaryRestart() - { - ddSecondaryTask = null; - secondaryCts.Dispose(); - }; - Task PrimaryCrash() => HandleServerCrashed(ddPrimaryTask, interop.SecondaryIsOther, cancellationToken); - Task SecondaryCrash() => HandleServerCrashed(ddSecondaryTask, !interop.SecondaryIsOther, cancellationToken); + if (ddSecondaryTask == null) + //start the secondary server + ddSecondaryTask = StartServer(initialLaunchParameters, null, control.SecondaryAccessToken, false, cancellationToken, out secondaryCts); - //update available - if (newDmbTask.IsCompleted) - { - //restart the other server but don't treat it as an error - launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); - //restart other server - if (interop.SecondaryIsOther) + var newDmbTask = dmbFactory.OnNewerDmb(); + + //now we wait for something to happen + await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, newDmbTask).ConfigureAwait(false); + + //some helpers + void PrimaryRestart() { - secondaryCts.Cancel(); - await ddSecondaryTask.ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + primaryCts.Dispose(); + ddPrimaryTask = StartServer(initialLaunchParameters, null, control.PrimaryAccessToken, true, cancellationToken, out primaryCts); + } + void SecondaryRestart() + { + ddSecondaryTask = null; + secondaryCts.Dispose(); + }; + Task PrimaryCrash() => HandleServerCrashed(ddPrimaryTask, control.SecondaryIsOther, cancellationToken); + Task SecondaryCrash() => HandleServerCrashed(ddSecondaryTask, !control.SecondaryIsOther, cancellationToken); + + //update available + if (newDmbTask.IsCompleted) + { + //restart the other server but don't treat it as an error + launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); + //restart other server + if (control.SecondaryIsOther) + { + secondaryCts.Cancel(); + await ddSecondaryTask.ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + SecondaryRestart(); + } + else + { + primaryCts.Cancel(); + await ddPrimaryTask.ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + PrimaryRestart(); + } + continue; + } + + //crash of both servers + if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) + { + //catastrophic, start over + var t1 = PrimaryCrash(); + await Task.WhenAll(t1, SecondaryCrash()).ConfigureAwait(false); + if (t1.Result) + return; + ++retries; + continue; + } + + //below this point: crash of single server + + //activate the other server and load new launch params + var otherServerActivation = control.ActivateOtherServer(cancellationToken); + launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); + await otherServerActivation.ConfigureAwait(false); + + //crash of secondary server + if (ddSecondaryTask.IsCompleted) + { + if (await SecondaryCrash().ConfigureAwait(false)) + return; SecondaryRestart(); } + //crash of primary server else { - primaryCts.Cancel(); - await ddPrimaryTask.ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + if (await PrimaryCrash().ConfigureAwait(false)) + return; PrimaryRestart(); } - continue; - } - - //crash of both servers - if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) - { - //catastrophic, start over - var t1 = PrimaryCrash(); - await Task.WhenAll(t1, SecondaryCrash()).ConfigureAwait(false); - if (t1.Result) - return; - ++retries; - continue; - } - - //below this point: crash of single server - - //activate the other server and load new launch params - var otherServerActivation = interop.ActivateOtherServer(cancellationToken); - launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); - await otherServerActivation.ConfigureAwait(false); - - //crash of secondary server - if (ddSecondaryTask.IsCompleted) - { - if (await SecondaryCrash().ConfigureAwait(false)) - return; - SecondaryRestart(); - } - //crash of primary server - else - { - if (await PrimaryCrash().ConfigureAwait(false)) - return; - PrimaryRestart(); - } - } while (true); + } while (true); + } + finally + { + secondaryCts?.Dispose(); + } } finally { - secondaryCts?.Dispose(); + primaryCts.Dispose(); } } - finally - { - primaryCts.Dispose(); - } } while (true); } From ff55be0f5bb4ff1ad73cbf8853ad4a4687dbf90e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 11:33:16 -0400 Subject: [PATCH 129/397] Improve dme modifications --- .../Components/DreamMaker.cs | 11 ++++-- .../Components/IConfiguration.cs | 4 +- .../Components/ServerSideModifications.cs | 38 +++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/ServerSideModifications.cs diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 4abd5e10b5..0300c94352 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -181,16 +181,21 @@ namespace Tgstation.Server.Host.Components var dmeModifications = await dmeModificationsTask.ConfigureAwait(false); - if (!dmeModifications.Any()) + if (dmeModifications == null || dmeModifications.TotalDmeOverwrite) return; var dmeLines = new List(dme.Split(new[] { Environment.NewLine }, StringSplitOptions.None)); for (var I = 0; I < dmeLines.Count; ++I) { var line = dmeLines[I]; - if (line.Contains("BEGIN_INCLUDE")) + if (line.Contains("BEGIN_INCLUDE") && dmeModifications.HeadIncludeLine != null) { - dmeLines.InsertRange(I + 1, dmeModifications); + dmeLines.Insert(I + 1, dmeModifications.HeadIncludeLine); + ++I; + } + else if (line.Contains("END_INCLUDE") && dmeModifications.TailIncludeLine != null) + { + dmeLines.Insert(I - 1, dmeModifications.TailIncludeLine); break; } } diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index 3a6f2f0ded..3b31dda93e 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -16,8 +16,8 @@ namespace Tgstation.Server.Host.Components /// /// Path to the destination folder /// The for the operation - /// A resultin in a of #include lines for the .dm files copied - Task> CopyDMFilesTo(string destination, CancellationToken cancellationToken); + /// A resulting in the if any + Task CopyDMFilesTo(string destination, CancellationToken cancellationToken); /// /// Symlinks all directories in the GameData directory to diff --git a/src/Tgstation.Server.Host/Components/ServerSideModifications.cs b/src/Tgstation.Server.Host/Components/ServerSideModifications.cs new file mode 100644 index 0000000000..a9edcb1ddc --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ServerSideModifications.cs @@ -0,0 +1,38 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents code modifications via configuration + /// + sealed class ServerSideModifications + { + /// + /// If the target dme was completely overwitten + /// + public bool TotalDmeOverwrite { get; } + + /// + /// The #include line which should be added to the beginning of the .dme if any + /// + public string HeadIncludeLine { get; } + + /// + /// The #include line which should be added to the end of the .dme if any + /// + public string TailIncludeLine { get; } + + /// + /// Construct + /// + /// The value of + /// The value of + /// The value of + public ServerSideModifications(string headIncludeLine, string tailIncludeLine, bool totalDmeOverwrite) + { + HeadIncludeLine = headIncludeLine; + TailIncludeLine = tailIncludeLine; + TotalDmeOverwrite = totalDmeOverwrite; + } + } +} From 10325efab6e5e67a154e5a382b5f100ec3d96012 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 11:36:40 -0400 Subject: [PATCH 130/397] Fix DMAPI --- src/DMAPI/tgs/v4/api.dm | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 981a30e1ad..5b836939cf 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -2,6 +2,7 @@ #define TGS4_TOPIC_COMMAND "tgs_com" #define TGS4_TOPIC_TOKEN "tgs_tok" +#define TGS4_TOPIC_SUCCESS "tgs_succ" #define TGS4_TOPIC_SWAP "tgs_swap" #define TGS4_TOPIC_SWAP_DELAYED "tgs_swap_delayed" #define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm" @@ -25,7 +26,7 @@ var/json_path var/list/intercepted_message_queue - + var/list/custom_commands var/list/cached_test_merges @@ -174,22 +175,22 @@ var/datum/tgs_chat_channel/channel = I ids += channel.id message = list("message" = message, "channels" = ids) - if(interepted_message_queue) - interepted_message_queue += list(message) + if(intercepted_message_queue) + intercepted_message_queue += list(message) else Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) message = list("message" = message, "channels" = admin_only ? "admin" : "game") - if(interepted_message_queue) - interepted_message_queue += list(message) + if(intercepted_message_queue) + intercepted_message_queue += list(message) else Export("[TGS4_COMM_CHAT] [json_encode(message)]") /datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) message = list("message" = message, "user" = list("id" = user.id, "channel" = user.channel.id)) - if(interepted_message_queue) - interepted_message_queue += list(message) + if(intercepted_message_queue) + intercepted_message_queue += list(message) else Export("[TGS4_COMM_CHAT] [json_encode(message)]") From 59407baf1dd21385052e5b523613a1567e5d9065 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 11:47:28 -0400 Subject: [PATCH 131/397] Clean up --- .../Components/DreamMaker.cs | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 0300c94352..19426d5d38 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -46,10 +45,6 @@ namespace Tgstation.Server.Host.Components /// The for /// readonly IInterop interop; - /// - /// The for - /// - readonly ICryptographySuite cryptographySuite; /// /// Construct @@ -60,8 +55,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - /// The value of - public DreamMaker(IRepositoryManager repositoryManager, IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICryptographySuite cryptographySuite) + public DreamMaker(IRepositoryManager repositoryManager, IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop) { this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -69,7 +63,6 @@ namespace Tgstation.Server.Host.Components this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); - this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); } /// @@ -84,36 +77,34 @@ namespace Tgstation.Server.Host.Components var launchParameters = new DreamDaemonLaunchParameters { AllowWebClient = false, - PrimaryPort = 0, //pick any port + PrimaryPort = 0, //pick any port SecurityLevel = DreamDaemonSecurity.Safe //all it needs to read the file and exit }; using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null)) { var interopInfo = new InteropInfo { ApiValidateOnly = true, HostPath = Application.HostingPath, - AccessToken = cryptographySuite.GetSecureString() + AccessToken = control.PrimaryAccessToken }; - - using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null)) + var ddTcs = new TaskCompletionSource(); + control.OnServerControl += (sender, e) => { - var ddTcs = new TaskCompletionSource(); - control.OnServerControl += (sender, e) => { - if (e.EventType == ServerControlEventType.ServerUnresponsive) - ddTcs.SetResult(null); - }; - var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, ".dmb")))), interopInfo, true, cts.Token); + if (e.EventType == ServerControlEventType.ServerUnresponsive) + ddTcs.SetResult(null); + }; + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, ".dmb")))), interopInfo, true, cts.Token); - await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); + await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); - if (!ddTestTask.IsCompleted) - cts.Cancel(); + if (!ddTestTask.IsCompleted) + cts.Cancel(); - return await ddTestTask.ConfigureAwait(false) == 0 && !ddTcs.Task.IsCompleted; - } + return await ddTestTask.ConfigureAwait(false) == 0 && !ddTcs.Task.IsCompleted; } } From 7f9cf6f29a4ca33425b19d9babaf0a3dd8d1d283 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 12:16:17 -0400 Subject: [PATCH 132/397] Removes unused using --- src/Tgstation.Server.Host/Components/DreamMaker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 19426d5d38..87508da8ca 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -9,7 +9,6 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Models; using Tgstation.Server.Host.Core; -using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components { From b8b652c7a5d446336aad14f7e013c0edcd2b1717 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 13:32:34 -0400 Subject: [PATCH 133/397] DreamMakerController --- src/Tgstation.Server.Api/Models/CompileJob.cs | 5 - .../Components/DreamMaker.cs | 108 ++++++++---------- .../Components/IDreamMaker.cs | 3 +- .../Components/Repository.cs | 2 +- .../Components/RepositoryManager.cs | 7 +- .../Controllers/DreamMakerController.cs | 93 +++++++++++++++ src/Tgstation.Server.Host/Core/IJobManager.cs | 10 +- src/Tgstation.Server.Host/Core/JobManager.cs | 19 ++- .../Models/CompileJob.cs | 9 +- .../Models/DatabaseContext.cs | 14 +-- .../Models/IDatabaseContext.cs | 10 ++ 11 files changed, 188 insertions(+), 92 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/DreamMakerController.cs diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index a3b97ed6f7..359d7a02df 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -8,11 +8,6 @@ /// public User TriggeredBy { get; set; } - /// - /// The that cancelled the job if any - /// - public User CancelledBy { get; set; } - /// /// Git revision the compiler ran on. Not modifiable /// diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 87508da8ca..eecc1b23d2 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -19,11 +19,7 @@ namespace Tgstation.Server.Host.Components /// Name of the primary directory used for compilation /// const string ADirectoryName = "A"; - - /// - /// The for - /// - readonly IRepositoryManager repositoryManager; + /// /// The for /// @@ -48,15 +44,13 @@ namespace Tgstation.Server.Host.Components /// /// Construct /// - /// The value of /// The value of /// The value of /// The value of /// The value of /// The value of - public DreamMaker(IRepositoryManager repositoryManager, IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop) + public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop) { - this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); @@ -195,7 +189,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task Compile(string dmeName, CancellationToken cancellationToken) + public async Task Compile(string dmeName, IRepository repository, CancellationToken cancellationToken) { var job = new Host.Models.CompileJob { @@ -203,68 +197,64 @@ namespace Tgstation.Server.Host.Components StartedAt = DateTimeOffset.Now, DmeName = dmeName }; - - await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); - var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B"); - - async Task CleanupFailedCompile() - { - try - { - await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); - } - catch { } - }; - try { - //copy the repository - var fullDirA = ioManager.ResolvePath(dirA); - using (var repository = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B"); + + async Task CleanupFailedCompile() { - job.RevisionInformation = new Host.Models.RevisionInformation + try { - Commit = repository.Head - }; - await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - } + await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); + } + catch { } + }; - await ModifyDme(job, cancellationToken).ConfigureAwait(false); - - //run compiler, verify api - var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + try { - await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + //copy the repository + var fullDirA = ioManager.ResolvePath(dirA); + using (repository) + await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - return await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); - }, true).ConfigureAwait(false); + await ModifyDme(job, cancellationToken).ConfigureAwait(false); - if(!ddVerified) - { - //server never validated - job.FinishedAt = DateTimeOffset.Now; - await CleanupFailedCompile().ConfigureAwait(false); + //run compiler, verify api + var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + { + await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + + return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); + }, true).ConfigureAwait(false); + + if (!ddVerified) + //server never validated or compile failed + await CleanupFailedCompile().ConfigureAwait(false); + else + { + job.DMApiValidated = true; + + //duplicate the dmb et al + await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); + + //symlink in the static data + var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); + await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); + await symATask.ConfigureAwait(false); + } return job; } - - job.DMApiValidated = true; - - //duplicate the dmb et al - await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); - - //symlink in the static data - var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); - await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); - await symATask.ConfigureAwait(false); - - job.FinishedAt = DateTimeOffset.Now; - return job; + catch + { + await CleanupFailedCompile().ConfigureAwait(false); + throw; + } } - catch + finally { - await CleanupFailedCompile().ConfigureAwait(false); - throw; + job.FinishedAt = DateTimeOffset.Now; } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 8e6036dc55..527add5a8f 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -14,8 +14,9 @@ namespace Tgstation.Server.Host.Components /// Starts a compile /// /// The .dme file to use without the extension + /// The to copy from /// The for the operation /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated - Task Compile(string dmeName, CancellationToken cancellationToken); + Task Compile(string dmeName, IRepository repository, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Repository.cs b/src/Tgstation.Server.Host/Components/Repository.cs index 20d8d569b4..50364ca9b1 100644 --- a/src/Tgstation.Server.Host/Components/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository.cs @@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Components public void Dispose() { repository.Dispose(); - onDispose(); + onDispose.Invoke(); } /// /// Convert to an "https://@{url} equivalent diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index 627ba44cfa..5982887adb 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -127,7 +127,12 @@ namespace Tgstation.Server.Host.Components { repo = new LibGit2Sharp.Repository(ioManager.ResolvePath(".")); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - return new Repository(repo, ioManager, () => semaphore.Release()); + var localSemaphore = semaphore; + return new Repository(repo, ioManager, () => + { + localSemaphore?.Release(); + localSemaphore = null; + }); } /// diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs new file mode 100644 index 0000000000..13ce786f5b --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// Controller for managing the compiler + /// + [Route("/DreamMaker")] + public sealed class DreamMakerController : ModelController + { + /// + /// The for the + /// + readonly IJobManager jobManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) => this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + + /// + [TgsAuthorize(DreamMakerRights.Compile)] + public override async Task Create([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken) + { + var job = new Job + { + Description = "Compile active repository code", + StartedBy = AuthenticationContext.User + }; + await jobManager.RegisterOperation(job, (serviceProvider, ct) => RunCompile(serviceProvider, Instance, AuthenticationContext.Clone(), ct), cancellationToken).ConfigureAwait(false); + return Json(job); + } + + /// + public override async Task Delete([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken) + { + //alias for cancelling the latest job + var job = await DatabaseContext.Jobs.OrderByDescending(x => x.StartedAt).Select(x => new Job { Id = x.Id, StoppedAt = x.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); + if (job.StoppedAt != null) + return StatusCode(HttpStatusCode.Gone); + jobManager.CancelJob(job); + return Ok(); + } + + /// + /// Run the compile job and insert it into the database + /// + /// The for the operation + /// The for the operation + /// The for the operation + /// The for the operation + /// + static async Task RunCompile(IServiceProvider serviceProvider, Models.Instance instanceModel, IAuthenticationContext authenticationContext, CancellationToken cancellationToken) + { + var instanceManager = serviceProvider.GetRequiredService(); + var databaseContext = serviceProvider.GetRequiredService(); + + var projectName = await databaseContext.Instances.Where(x => x.Id == instanceModel.Id).Select(x => x.DreamMakerSettings.ProjectName).FirstAsync(cancellationToken).ConfigureAwait(false); + + var instance = instanceManager.GetInstance(instanceModel); + + CompileJob compileJob; + Task revInfoTask; + using (var repo = await instance.RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + revInfoTask = databaseContext.RevisionInformations.Where(x => x.Commit == repo.Head).Select(x => new RevisionInformation { Id = x.Id }).FirstAsync(); + compileJob = await instance.DreamMaker.Compile(projectName, repo, cancellationToken).ConfigureAwait(false); + } + + compileJob.TriggeredBy = authenticationContext.User; + compileJob.RevisionInformation = await revInfoTask.ConfigureAwait(false); + + databaseContext.CompileJobs.Add(compileJob); + //default ct because we don't want to give up after getting this far + await databaseContext.Save(default).ConfigureAwait(false); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 13d30ad436..ce5798ac3f 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -1,11 +1,15 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { - interface IJobManager + /// + /// Manages the runtime of s + /// + public interface IJobManager { /// /// Registers a given and begins running it @@ -14,7 +18,7 @@ namespace Tgstation.Server.Host.Core /// The operation to run /// The for the operation /// A representing a running operation - Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken); + Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken); /// /// Wait for a given to complete diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index e81f8ece73..995b544a8f 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -53,16 +53,23 @@ namespace Tgstation.Server.Host.Core /// The operation for the /// The for the operation /// A representing the running operation - async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) + async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) { using (var scope = serviceProvider.CreateScope()) { - var databaseContext = scope.ServiceProvider.GetRequiredService(); - job = new Job { Id = job.Id }; - databaseContext.Jobs.Attach(job); + IDatabaseContext databaseContext = null; try { - await operation(cancellationToken).ConfigureAwait(false); + try + { + await operation(scope.ServiceProvider, cancellationToken).ConfigureAwait(false); + } + finally + { + job = new Job { Id = job.Id }; + databaseContext = scope.ServiceProvider.GetRequiredService(); + databaseContext.Jobs.Attach(job); + } } catch (OperationCanceledException) { @@ -78,7 +85,7 @@ namespace Tgstation.Server.Host.Core } /// - public async Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken) + public async Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken) { using (var scope = serviceProvider.CreateScope()) { diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index a22e7ec5c3..8a34707703 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.DataAnnotations; - -namespace Tgstation.Server.Host.Models +namespace Tgstation.Server.Host.Models { /// public sealed class CompileJob : Api.Models.Internal.CompileJob @@ -10,11 +8,6 @@ namespace Tgstation.Server.Host.Models /// public User TriggeredBy { get; set; } - /// - /// See - /// - public User CancelledBy { get; set; } - /// /// See /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 5f0ffef80f..3d848add3e 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -22,6 +22,12 @@ namespace Tgstation.Server.Host.Models /// public DbSet Instances { get; set; } + /// + public DbSet CompileJobs { get; set; } + + /// + public DbSet RevisionInformations { get; set; } + /// /// The for s /// @@ -48,10 +54,6 @@ namespace Tgstation.Server.Host.Models /// public DbSet DreamMakerSettings { get; set; } /// - /// The s in the - /// - public DbSet CompileJobs { get; set; } - /// /// The s in the /// public DbSet Jobs { get; set; } @@ -60,10 +62,6 @@ namespace Tgstation.Server.Host.Models /// public DbSet TestMerges { get; set; } /// - /// The s in the - /// - public DbSet RevisionInformations { get; set; } - /// /// The in the /// public DbSet RepositorySettings { get; set; } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index c9076a354b..da1bea8f12 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -24,6 +24,16 @@ namespace Tgstation.Server.Host.Models /// DbSet Jobs { get; } + /// + /// The s in the + /// + DbSet CompileJobs { get; } + + /// + /// The s in the + /// + DbSet RevisionInformations { get; } + /// /// Get the in the /// From 40ffe4bf2f4a8f69faeeb7b1000ab1790eb8cc29 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 13:48:42 -0400 Subject: [PATCH 134/397] Cleanup cleanup --- src/Tgstation.Server.Api/Models/CompileJob.cs | 4 +- .../Models/Internal/CompileJob.cs | 12 --- .../Models/Internal/Job.cs | 13 +++ src/Tgstation.Server.Api/Models/Job.cs | 12 +-- .../Components/DreamMaker.cs | 100 ++++++++---------- .../Controllers/DreamMakerController.cs | 15 +-- src/Tgstation.Server.Host/Core/IJobManager.cs | 4 +- src/Tgstation.Server.Host/Core/JobManager.cs | 9 +- .../Models/CompileJob.cs | 15 ++- 9 files changed, 95 insertions(+), 89 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 359d7a02df..211ff95951 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -4,9 +4,9 @@ public sealed class CompileJob : Internal.CompileJob { /// - /// The that triggered the job + /// The relating to this job /// - public User TriggeredBy { get; set; } + public Job Job { get; set; } /// /// Git revision the compiler ran on. Not modifiable diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index 0833d79e34..7f717c121c 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -13,18 +13,6 @@ namespace Tgstation.Server.Api.Models.Internal /// public long Id { get; set; } - /// - /// When the compilation started - /// - [Required] - public DateTimeOffset StartedAt { get; set; } - - /// - /// When the compilation finished - /// - [Required] - public DateTimeOffset FinishedAt { get; set; } - /// /// If the DMAPI version detected is compatible /// diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index 205faa7707..1b0104c22d 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -1,4 +1,5 @@ using System; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -43,5 +44,17 @@ namespace Tgstation.Server.Api.Models.Internal /// [Permissions(DenyWrite = true)] public bool Cancelled { get; set; } + + /// + /// The of if it can be cancelled + /// + [Permissions(DenyWrite = true)] + public RightsType? CancelRightsType { get; set; } + + /// + /// The required to cancel the + /// + [Permissions(DenyWrite = true)] + public int? CancelRight { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs index ebbf23bb21..b1141f3400 100644 --- a/src/Tgstation.Server.Api/Models/Job.cs +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -11,16 +11,16 @@ [Permissions(DenyWrite = true)] public int Progress { get; set; } - /// - /// If the current user has permission to cancel the job - /// - [Permissions(DenyWrite = true)] - public bool UserCanCancel { get; set; } - /// /// The that started the job /// [Permissions(DenyWrite = true)] public User StartedBy { get; set; } + + /// + /// The that cancelled the job + /// + [Permissions(DenyWrite = true)] + public User CancelledBy { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index eecc1b23d2..f43598ccdb 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -194,67 +194,59 @@ namespace Tgstation.Server.Host.Components var job = new Host.Models.CompileJob { DirectoryName = Guid.NewGuid(), - StartedAt = DateTimeOffset.Now, DmeName = dmeName }; - try + await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B"); + + async Task CleanupFailedCompile() { - await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); - var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B"); - - async Task CleanupFailedCompile() - { - try - { - await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); - } - catch { } - }; - try { - //copy the repository - var fullDirA = ioManager.ResolvePath(dirA); - using (repository) - await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - - await ModifyDme(job, cancellationToken).ConfigureAwait(false); - - //run compiler, verify api - var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => - { - await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); - - return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); - }, true).ConfigureAwait(false); - - if (!ddVerified) - //server never validated or compile failed - await CleanupFailedCompile().ConfigureAwait(false); - else - { - job.DMApiValidated = true; - - //duplicate the dmb et al - await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); - - //symlink in the static data - var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); - await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); - await symATask.ConfigureAwait(false); - } - return job; + await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); } - catch - { - await CleanupFailedCompile().ConfigureAwait(false); - throw; - } - } - finally + catch { } + }; + + try { - job.FinishedAt = DateTimeOffset.Now; + //copy the repository + var fullDirA = ioManager.ResolvePath(dirA); + using (repository) + await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); + + await ModifyDme(job, cancellationToken).ConfigureAwait(false); + + //run compiler, verify api + var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + { + await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + + return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); + }, true).ConfigureAwait(false); + + if (!ddVerified) + //server never validated or compile failed + await CleanupFailedCompile().ConfigureAwait(false); + else + { + job.DMApiValidated = true; + + //duplicate the dmb et al + await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); + + //symlink in the static data + var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); + await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); + await symATask.ConfigureAwait(false); + } + return job; + } + catch + { + await CleanupFailedCompile().ConfigureAwait(false); + throw; } } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 13ce786f5b..6b849d8636 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -40,13 +40,16 @@ namespace Tgstation.Server.Host.Controllers var job = new Job { Description = "Compile active repository code", - StartedBy = AuthenticationContext.User + StartedBy = AuthenticationContext.User, + CancelRightsType = RightsType.DreamMaker, + CancelRight = (int)DreamMakerRights.CancelCompile }; - await jobManager.RegisterOperation(job, (serviceProvider, ct) => RunCompile(serviceProvider, Instance, AuthenticationContext.Clone(), ct), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); return Json(job); } /// + [TgsAuthorize(DreamMakerRights.CancelCompile)] public override async Task Delete([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken) { //alias for cancelling the latest job @@ -60,12 +63,12 @@ namespace Tgstation.Server.Host.Controllers /// /// Run the compile job and insert it into the database /// + /// The running /// The for the operation /// The for the operation - /// The for the operation /// The for the operation - /// - static async Task RunCompile(IServiceProvider serviceProvider, Models.Instance instanceModel, IAuthenticationContext authenticationContext, CancellationToken cancellationToken) + /// A representing the running operation + static async Task RunCompile(Job job, IServiceProvider serviceProvider, Models.Instance instanceModel, CancellationToken cancellationToken) { var instanceManager = serviceProvider.GetRequiredService(); var databaseContext = serviceProvider.GetRequiredService(); @@ -82,7 +85,7 @@ namespace Tgstation.Server.Host.Controllers compileJob = await instance.DreamMaker.Compile(projectName, repo, cancellationToken).ConfigureAwait(false); } - compileJob.TriggeredBy = authenticationContext.User; + compileJob.Job = job; compileJob.RevisionInformation = await revInfoTask.ConfigureAwait(false); databaseContext.CompileJobs.Add(compileJob); diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index ce5798ac3f..0912a64890 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -15,10 +15,10 @@ namespace Tgstation.Server.Host.Core /// Registers a given and begins running it /// /// The - /// The operation to run + /// The operation to run taking the started , a and a /// The for the operation /// A representing a running operation - Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken); + Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken); /// /// Wait for a given to complete diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 995b544a8f..0676f2cc17 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -53,20 +53,21 @@ namespace Tgstation.Server.Host.Core /// The operation for the /// The for the operation /// A representing the running operation - async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) + async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) { using (var scope = serviceProvider.CreateScope()) { IDatabaseContext databaseContext = null; try { + var oldJob = job; + job = new Job { Id = oldJob.Id }; try { - await operation(scope.ServiceProvider, cancellationToken).ConfigureAwait(false); + await operation(job, scope.ServiceProvider, cancellationToken).ConfigureAwait(false); } finally { - job = new Job { Id = job.Id }; databaseContext = scope.ServiceProvider.GetRequiredService(); databaseContext.Jobs.Attach(job); } @@ -85,7 +86,7 @@ namespace Tgstation.Server.Host.Core } /// - public async Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken) + public async Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken) { using (var scope = serviceProvider.CreateScope()) { diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 8a34707703..f15efd5859 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -1,16 +1,25 @@ -namespace Tgstation.Server.Host.Models +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models { /// public sealed class CompileJob : Api.Models.Internal.CompileJob { /// - /// See + /// The of /// - public User TriggeredBy { get; set; } + public long JobId { get; set; } + + /// + /// See + /// + [Required] + public Job Job { get; set; } /// /// See /// + [Required] public RevisionInformation RevisionInformation { get; set; } } } From 523e19fbf72f474d6d02887bec3dcb82f83e2bba Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 15:50:04 -0400 Subject: [PATCH 135/397] DmbFactory and Provider --- .../Components/DmbFactory.cs | 134 ++++++++++++++++++ .../Components/DmbProvider.cs | 58 ++++++++ .../Components/DreamMaker.cs | 19 ++- .../Components/ICompileJobConsumer.cs | 11 ++ .../Controllers/DreamMakerController.cs | 5 +- .../Core/DefaultIOManager.cs | 13 ++ src/Tgstation.Server.Host/Core/IIOManager.cs | 8 ++ 7 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/DmbFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/DmbProvider.cs create mode 100644 src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs new file mode 100644 index 0000000000..2fd12b08a2 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -0,0 +1,134 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Standard + /// + sealed class DmbFactory : IDmbFactory, ICompileJobConsumer, IHostedService, IDisposable + { + /// + /// The for the + /// + readonly IDatabaseContext databaseContext; + /// + /// The for the + /// + readonly IIOManager ioManager; + /// + /// The for + /// + readonly CancellationTokenSource cleanupCts; + + /// + /// representing calls to + /// + Task cleanupTask; + /// + /// resulting in the latest yet to exist + /// + TaskCompletionSource newerDmbTcs; + /// + /// The latest + /// + DmbProvider nextDmbProvider; + + /// + /// Construct a + /// + /// The value of + /// The value of + public DmbFactory(IDatabaseContext databaseContext, IIOManager ioManager) + { + this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + + cleanupCts = new CancellationTokenSource(); + } + + /// + public void Dispose() => cleanupCts.Dispose(); + + /// + /// Delete the of + /// + /// The to clean + void CleanJob(CompileJob job) + { + async Task HandleCleanup() + { + var deleteJob = ioManager.DeleteDirectory(job.DirectoryName.ToString(), cleanupCts.Token); + Task otherTask; + lock (this) + otherTask = cleanupTask; + await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(false); + } + lock (this) + cleanupTask = HandleCleanup(); + } + + /// + public void LoadCompileJob(CompileJob job) + { + if (job == null) + throw new ArgumentNullException(nameof(job)); + if (!job.DMApiValidated || job.Job.Cancelled || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null) + throw new InvalidOperationException("Cannot load incomplete compile job!"); + lock (this) + { + var oldDmbProvider = nextDmbProvider; + if (oldDmbProvider != null && oldDmbProvider.CompileJob.Job.StoppedAt < oldDmbProvider.CompileJob.Job.StoppedAt) + throw new InvalidOperationException("Loaded compile job older than current job!"); + nextDmbProvider = new DmbProvider(job, ioManager, () => CleanJob(job)); + newerDmbTcs.SetResult(nextDmbProvider); + newerDmbTcs = new TaskCompletionSource(); + } + } + + /// + public async Task LockNextDmb(CancellationToken cancellationToken) + { + Task task; + lock (this) + if (nextDmbProvider != null) + return nextDmbProvider; + else + task = newerDmbTcs.Task; + + return await task.ConfigureAwait(false); + } + + /// + public Task OnNewerDmb() + { + lock (this) + return newerDmbTcs.Task; + } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + var cj = await databaseContext.CompileJobs.OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (cj == default(CompileJob)) + return; + LoadCompileJob(cj); + //delete all other compile jobs + var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false); + await Task.WhenAll(directories.Where(x => x != cj.Job.ToString()).Select(x => ioManager.DeleteDirectory(x, cancellationToken))).ConfigureAwait(false); + } + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + using (cancellationToken.Register(() => cleanupCts.Cancel())) + await cleanupTask.ConfigureAwait(false); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/DmbProvider.cs b/src/Tgstation.Server.Host/Components/DmbProvider.cs new file mode 100644 index 0000000000..0cd3f758f1 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DmbProvider.cs @@ -0,0 +1,58 @@ +using System; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class DmbProvider : IDmbProvider + { + /// + public string DmbName => String.Concat(CompileJob.DmeName, DreamMaker.DmbExtension); + + /// + public string PrimaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.ADirectoryName)); + + /// + public string SecondaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.BDirectoryName)); + + /// + public RevisionInformation RevisionInformation => CompileJob.RevisionInformation; + + /// + /// The for the + /// + public CompileJob CompileJob { get; } + + /// + /// The for the + /// + readonly IIOManager ioManager; + /// + /// The to run when is called + /// + readonly Action onDispose; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + public DmbProvider(CompileJob compileJob, IIOManager ioManager, Action onDispose) + { + CompileJob = compileJob ?? throw new ArgumentNullException(nameof(compileJob)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); + } + + ~DmbProvider() => Dispose(); + + /// + public void Dispose() + { + onDispose(); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index f43598ccdb..8469838b66 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -18,8 +18,15 @@ namespace Tgstation.Server.Host.Components /// /// Name of the primary directory used for compilation /// - const string ADirectoryName = "A"; - + public const string ADirectoryName = "A"; + /// + /// Name of the secondary directory used for compilation + /// + public const string BDirectoryName = "B"; + public const string DmbExtension = ".dmb"; + + const string DmeExtension = ".dme"; + /// /// The for /// @@ -90,7 +97,7 @@ namespace Tgstation.Server.Host.Components ddTcs.SetResult(null); }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, ".dmb")))), interopInfo, true, cts.Token); + var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, cts.Token); await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); @@ -113,7 +120,7 @@ namespace Tgstation.Server.Host.Components using (var dm = new Process()) { dm.StartInfo.FileName = dreamMakerPath; - dm.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "-clean {0}.dme", job.DmeName); + dm.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "-clean {0}{1}", job.DmeName, DmeExtension); dm.StartInfo.WorkingDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)); dm.StartInfo.RedirectStandardOutput = true; dm.StartInfo.RedirectStandardError = true; @@ -155,7 +162,7 @@ namespace Tgstation.Server.Host.Components async Task ModifyDme(Host.Models.CompileJob job, CancellationToken cancellationToken) { var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var dmePath = ioManager.ConcatPath(dirA, String.Concat(job.DmeName, ".dme")); + var dmePath = ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmeExtension)); var dmeReadTask = ioManager.ReadAllBytes(dmePath, cancellationToken); var dmeModificationsTask = configuration.CopyDMFilesTo(ioManager.ResolvePath(dirA), cancellationToken); @@ -198,7 +205,7 @@ namespace Tgstation.Server.Host.Components }; await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B"); + var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName); async Task CleanupFailedCompile() { diff --git a/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs new file mode 100644 index 0000000000..0877e9516c --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components +{ + interface ICompileJobConsumer + { + void LoadCompileJob(CompileJob job); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 6b849d8636..56586f7bfd 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -31,7 +31,10 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) => this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) + { + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + } /// [TgsAuthorize(DreamMakerRights.Compile)] diff --git a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs index f0644d6f33..f1cc847bc4 100644 --- a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs @@ -203,5 +203,18 @@ namespace Tgstation.Server.Host.Core using (var file = File.Open(path, FileMode.Create, FileAccess.Write)) await file.WriteAsync(contents, 0, contents.Length, cancellationToken).ConfigureAwait(false); } + + /// + public Task> GetDirectories(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + path = ResolvePath(path); + var results = new List(); + foreach(var I in Directory.EnumerateDirectories(path)) + { + cancellationToken.ThrowIfCancellationRequested(); + results.Add(I); + } + return (IReadOnlyList)results; + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Core/IIOManager.cs b/src/Tgstation.Server.Host/Core/IIOManager.cs index 2083ad84aa..38df374e63 100644 --- a/src/Tgstation.Server.Host/Core/IIOManager.cs +++ b/src/Tgstation.Server.Host/Core/IIOManager.cs @@ -56,6 +56,14 @@ namespace Tgstation.Server.Host.Core /// A that results in the contents of a file at Task ReadAllBytes(string path, CancellationToken cancellationToken); + /// + /// Returns directory names in a given + /// + /// The path to search for directories + /// The for the operation + /// A resulting in the directories in + Task> GetDirectories(string path, CancellationToken cancellationToken); + /// /// Writes some to a file at overwriting previous content /// From dd6dd658240908fee77d3b08141a2ea5226e0009 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 16:25:12 -0400 Subject: [PATCH 136/397] Pull the chains slightly tighter --- .../Components/DmbFactory.cs | 10 +++++++--- .../Components/DreamMaker.cs | 9 ++++++++- .../Components/ICompileJobConsumer.cs | 6 +++--- .../Components/IInstanceFactory.cs | 3 ++- .../Components/IInstanceManager.cs | 7 +++++-- src/Tgstation.Server.Host/Components/Instance.cs | 11 +++++++---- .../Components/InstanceFactory.cs | 16 +++++++++++++++- .../Components/InstanceManager.cs | 12 ++++++------ 8 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index 2fd12b08a2..cce071c63f 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Hosting; using System; using System.Linq; using System.Threading; @@ -12,7 +11,7 @@ namespace Tgstation.Server.Host.Components /// /// Standard /// - sealed class DmbFactory : IDmbFactory, ICompileJobConsumer, IHostedService, IDisposable + sealed class DmbFactory : IDmbFactory, ICompileJobConsumer { /// /// The for the @@ -102,7 +101,12 @@ namespace Tgstation.Server.Host.Components else task = newerDmbTcs.Task; - return await task.ConfigureAwait(false); + var result = await task.ConfigureAwait(false); + //so there's currently a race condition in DreamMakerController where the setting of CompileJob.RevisionInformation and thus IDmbProvider.RevisionInformation can be delayed to after this if someone tries to start the server instantly after compiling + //This is a terrible terrible hack to get around that + //I'm sorry future me, I can't think of any other way to fix this other than giving DreamMaker an IDatabaseContext or having the controller load the CompileJob + await Task.Delay(new TimeSpan(0, 0, 10), cancellationToken).ConfigureAwait(false); + return result; } /// diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 8469838b66..f49e1ad9d5 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -47,6 +47,10 @@ namespace Tgstation.Server.Host.Components /// The for /// readonly IInterop interop; + /// + /// The for + /// + readonly ICompileJobConsumer compileJobConsumer; /// /// Construct @@ -56,13 +60,15 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop) + /// The value of + public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICompileJobConsumer compileJobConsumer) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); + this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); } /// @@ -248,6 +254,7 @@ namespace Tgstation.Server.Host.Components await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); await symATask.ConfigureAwait(false); } + compileJobConsumer.LoadCompileJob(job); return job; } catch diff --git a/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs index 0877e9516c..2e96eb26a9 100644 --- a/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs +++ b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs @@ -1,10 +1,10 @@ -using System.Threading; -using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using System; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { - interface ICompileJobConsumer + interface ICompileJobConsumer : IHostedService, IDisposable { void LoadCompileJob(CompileJob job); } diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index 21d905f7a4..7dfd5a667d 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -11,7 +11,8 @@ namespace Tgstation.Server.Host.Components /// Create an /// /// The + /// The for the operation /// A new - IInstance CreateInstance(Host.Models.Instance metadata); + IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index 34c5f6123c..c087ad362a 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -1,5 +1,6 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { @@ -19,9 +20,10 @@ namespace Tgstation.Server.Host.Components /// Online an /// /// The of the desired + /// The for the operation /// The for the operation /// A representing the running operation - Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken); + Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken); /// /// Offline an @@ -35,9 +37,10 @@ namespace Tgstation.Server.Host.Components /// Move an /// /// The of the desired + /// The for the operation /// The new path of the . will have this set on if the operation completes successfully /// The for the operation /// A representing the running operation - Task MoveInstance(Host.Models.Instance metadata, string newPath, CancellationToken cancellationToken); + Task MoveInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 454b1063de..a7cc23f1db 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -17,10 +17,12 @@ namespace Tgstation.Server.Host.Components public IChat Chat { get; } public IConfiguration Configuration { get; } - + + readonly ICompileJobConsumer compileJobConsumer; + readonly Api.Models.Instance metadata; - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration) + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); @@ -29,6 +31,7 @@ namespace Tgstation.Server.Host.Components DreamDaemon = dreamDaemon ?? throw new ArgumentNullException(nameof(dreamDaemon)); Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); } public Api.Models.Instance GetMetadata() => metadata.CloneMetadata(); @@ -40,8 +43,8 @@ namespace Tgstation.Server.Host.Components metadata.Name = newName; } - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken)); + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)); - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index d241d749de..ed4955194e 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -1,16 +1,25 @@ using System; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { /// sealed class InstanceFactory : IInstanceFactory { + /// + /// The for the + /// readonly IIOManager ioManager; + /// + /// Construct an + /// + /// The value of public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + /// - public IInstance CreateInstance(Host.Models.Instance metadata) + public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext) { //Create the ioManager for the instance @@ -21,6 +30,11 @@ namespace Tgstation.Server.Host.Components var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond"); var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); + var codeModificationsIoMananger = new ResolvingIOManager(instanceIoManager, "CodeModifications"); + + var dmbFactory = new DmbFactory(databaseContext, gameIoManager); + + throw new NotImplementedException(); } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index ed93f70fa9..1b01e2b8e9 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task MoveInstance(Host.Models.Instance instance, string newPath, CancellationToken cancellationToken) + public async Task MoveInstance(Host.Models.Instance instance, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken) { if (newPath == null) throw new ArgumentNullException(nameof(newPath)); @@ -72,14 +72,14 @@ namespace Tgstation.Server.Host.Components var oldPath = instance.Path; await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); instance.Path = ioManager.ResolvePath(newPath); - instanceOnlineTask = OnlineInstance(instance, default); + instanceOnlineTask = OnlineInstance(instance, databaseContext, default); await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); } finally { if (instance.Online) if (instanceOnlineTask == null) - await OnlineInstance(instance, default).ConfigureAwait(false); + await OnlineInstance(instance, databaseContext, default).ConfigureAwait(false); else await instanceOnlineTask.ConfigureAwait(false); } @@ -101,11 +101,11 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) + public async Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken) { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); - var instance = instanceFactory.CreateInstance(metadata); + var instance = instanceFactory.CreateInstance(metadata, databaseContext); lock (this) { if (instances.ContainsKey(metadata.Id)) @@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Components await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); var tasks = new List(); - await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false); + await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, databaseContext, cancellationToken)), cancellationToken).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false); } } From 2dd1166a997c3c2d07e5330139d1633874d71cf8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 4 May 2018 16:28:22 -0400 Subject: [PATCH 137/397] Readd DM IHostedService impl --- src/Tgstation.Server.Host/Components/DreamMaker.cs | 13 +++++++++++++ src/Tgstation.Server.Host/Components/IDreamMaker.cs | 2 +- src/Tgstation.Server.Host/Components/Instance.cs | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index f49e1ad9d5..441b45de40 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -263,5 +263,18 @@ namespace Tgstation.Server.Host.Components throw; } } + + /// + public Task StartAsync(CancellationToken cancellationToken) + { + //THIS IS FOR AUTO COMPILE INTERVAL STUFF, STOP TRYING TO REMOVE IT + throw new NotImplementedException(); + } + + /// + public Task StopAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 527add5a8f..3e7c52eca5 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing the compiler /// - interface IDreamMaker + interface IDreamMaker : IHostedService { /// /// Starts a compile diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index a7cc23f1db..475c12e3d4 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -43,8 +43,8 @@ namespace Tgstation.Server.Host.Components metadata.Name = newName; } - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)); + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), DreamMaker.StartAsync(cancellationToken)); - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), DreamMaker.StopAsync(cancellationToken)); } } From 4d318a78602aec77ec41fae10c382d639a74d964 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 6 May 2018 09:12:26 -0400 Subject: [PATCH 138/397] Add missing TRUE return to v3202 API --- src/DMAPI/tgs/v3210/api.dm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/v3210/api.dm b/src/DMAPI/tgs/v3210/api.dm index 5f69889f07..2302249763 100644 --- a/src/DMAPI/tgs/v3210/api.dm +++ b/src/DMAPI/tgs/v3210/api.dm @@ -57,6 +57,8 @@ return splittext(trim_left(trim_right(file2text(filename))), "\n") /datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version + . = FALSE + comms_key = world.params[SERVICE_WORLD_PARAM] instance_name = world.params[SERVICE_INSTANCE_PARAM] if(!instance_name) @@ -72,9 +74,10 @@ if(world.system_type != MS_WINDOWS) TGS_ERROR_LOG("This API version is only supported on Windows. Not running on Windows. Aborting initialization!") - return FALSE + return ListServiceCustomCommands(TRUE) ExportService("[SERVICE_REQUEST_API_VERSION] [ApiVersion()]", TRUE) + return TRUE //nothing to do for v3 /datum/tgs_api/v3210/OnInitializationComplete() From 9caa684eb82918967c5274035804ab677f0b333d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 6 May 2018 09:13:53 -0400 Subject: [PATCH 139/397] Fix activation message occuring for failed instatiations --- src/DMAPI/tgs/core/core.dm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index ffeab4289b..3061188e39 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -7,12 +7,14 @@ 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.") + TGS_INFO_LOG("Activating API for version [tgs_version]") var/datum/tgs_api/new_api = new path - TGS_INFO_LOG("Activated tgstation-server API for version [tgs_version]") var/result = new_api.OnWorldNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) if(result && result != TGS_UNIMPLEMENTED) TGS_WRITE_GLOBAL(tgs, new_api) + else + TGS_ERROR_LOG("Failed to activate API!") /world/proc/SelectTgsApi(tgs_version) var/list/version_bits = splittext(tgs_version, ".") From 1e272e27471840016cf3645b2ac3290b0af8500b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 6 May 2018 11:24:19 -0400 Subject: [PATCH 140/397] oh man --- src/DMAPI/tgs/core/core.dm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index 3061188e39..61f1e71d2e 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -17,12 +17,15 @@ 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[0]) - var/major = text2num(version_bits[1]) - var/minor = text2num(version_bits[2]) - var/patch = text2num(version_bits[3]) + 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) From 2e56acad5c87683f59361e0c9a2157b530533915 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 6 May 2018 12:32:16 -0400 Subject: [PATCH 141/397] Fix v3 api not building chat user datum --- src/DMAPI/tgs/v3210/commands.dm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/v3210/commands.dm b/src/DMAPI/tgs/v3210/commands.dm index e823c16d26..5046631981 100644 --- a/src/DMAPI/tgs/v3210/commands.dm +++ b/src/DMAPI/tgs/v3210/commands.dm @@ -40,7 +40,10 @@ if(!command_type) return FALSE var/datum/tgs_chat_command/stc = new command_type - return stc.Run(sender, params) || TRUE + var/datum/tgs_chat_user/user = new + user.friendly_name = sender + user.mention = sender + return stc.Run(user, params) || TRUE /* From 720863d55e76db19a786db36c1ac10015e1f29c5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 10:06:01 -0400 Subject: [PATCH 142/397] Remove timer shennanigans from repository. Fix null project name handling. Add IDatabaseContextFactory --- src/Tgstation.Server.Api/Models/Instance.cs | 6 + .../Models/Internal/DreamMakerSettings.cs | 6 - .../Models/Internal/RepositorySettings.cs | 6 - .../Rights/DreamMakerRights.cs | 6 +- .../Rights/InstanceManagerRights.cs | 6 +- .../Rights/RepositoryRights.cs | 10 +- .../Components/DmbFactory.cs | 16 +-- .../Components/DreamMaker.cs | 36 +++--- .../Components/IDreamMaker.cs | 6 +- .../Components/IInstance.cs | 3 + .../Components/IInstanceFactory.cs | 6 +- .../Components/IRepositoryManager.cs | 11 +- .../Components/Instance.cs | 110 +++++++++++++++++- .../Components/InstanceFactory.cs | 5 +- .../Components/RepositoryManager.cs | 82 +------------ .../Core/IDatabaseContextFactory.cs | 19 +++ 16 files changed, 181 insertions(+), 153 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 22bd31e1be..74910fdf68 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -41,6 +41,12 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = InstanceManagerRights.SetConfiguration)] public bool ConfigurationAllowed { get; set; } + /// + /// The time interval in minutes the repository is automatically pulled and compiles + /// + [Permissions(WriteRight = InstanceManagerRights.SetAutoUpdate)] + public int? AutoUpdateInterval { get; set; } + /// public Instance CloneMetadata() => new Instance { diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs index 2e07ee683a..154167ec82 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs @@ -8,12 +8,6 @@ namespace Tgstation.Server.Api.Models.Internal [Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)] public class DreamMakerSettings { - /// - /// How often the automatically compiles in minutes - /// - [Permissions(WriteRight = DreamMakerRights.SetAutoCompile)] - public int? AutoCompileInterval { get; set; } - /// /// The .dme file tries to compile with without the extension /// diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index 35a560f7da..5893293fa7 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -46,11 +46,5 @@ namespace Tgstation.Server.Api.Models.Internal /// [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] public bool ShowTestMergeCommitters { get; set; } - - /// - /// How often the automatically updates in minutes - /// - [Permissions(WriteRight = RepositoryRights.ChangeAutoUpdate)] - public int? AutoUpdateInterval { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index 661530c95a..bce942c165 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -25,12 +25,8 @@ namespace Tgstation.Server.Api.Rights /// CancelCompile = 4, /// - /// User may modify - /// - SetAutoCompile = 8, - /// /// User may modify /// - SetDme = 16 + SetDme = 8 } } diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index 106eaa1ad1..fbec63f9d5 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -43,6 +43,10 @@ namespace Tgstation.Server.Api.Rights /// /// User can change /// - SetConfiguration = 128 + SetConfiguration = 128, + /// + /// User can change + /// + SetAutoUpdate = 256 } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index c418507036..23703fd446 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -37,20 +37,16 @@ namespace Tgstation.Server.Api.Rights /// ChangeTestMergeCommits = 64, /// - /// User may change - /// - ChangeAutoUpdate = 128, - /// /// User may read and change and /// - ChangeCredentials = 256, + ChangeCredentials = 128, /// /// User may set to another git reference (not a SHA) /// - SetReference = 512, + SetReference = 256, /// /// User may read all fields in the with the exception of /// - Read = 1024, + Read = 512, } } diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index cce071c63f..9d21f4747f 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -14,9 +14,9 @@ namespace Tgstation.Server.Host.Components sealed class DmbFactory : IDmbFactory, ICompileJobConsumer { /// - /// The for the + /// The for the /// - readonly IDatabaseContext databaseContext; + readonly IDatabaseContextFactory databaseContextFactory; /// /// The for the /// @@ -42,11 +42,11 @@ namespace Tgstation.Server.Host.Components /// /// Construct a /// - /// The value of + /// The value of /// The value of - public DmbFactory(IDatabaseContext databaseContext, IIOManager ioManager) + public DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager) { - this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); cleanupCts = new CancellationTokenSource(); @@ -117,16 +117,16 @@ namespace Tgstation.Server.Host.Components } /// - public async Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { - var cj = await databaseContext.CompileJobs.OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var cj = await db.CompileJobs.OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (cj == default(CompileJob)) return; LoadCompileJob(cj); //delete all other compile jobs var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false); await Task.WhenAll(directories.Where(x => x != cj.Job.ToString()).Select(x => ioManager.DeleteDirectory(x, cancellationToken))).ConfigureAwait(false); - } + }); /// public async Task StopAsync(CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 441b45de40..f70df87e5e 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -23,8 +24,13 @@ namespace Tgstation.Server.Host.Components /// Name of the secondary directory used for compilation /// public const string BDirectoryName = "B"; + /// + /// Extension for .dmbs + /// public const string DmbExtension = ".dmb"; - + /// + /// Extension for .dmes + /// const string DmeExtension = ".dme"; /// @@ -61,6 +67,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICompileJobConsumer compileJobConsumer) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -202,12 +209,12 @@ namespace Tgstation.Server.Host.Components } /// - public async Task Compile(string dmeName, IRepository repository, CancellationToken cancellationToken) + public async Task Compile(string projectName, IRepository repository, CancellationToken cancellationToken) { var job = new Host.Models.CompileJob { DirectoryName = Guid.NewGuid(), - DmeName = dmeName + DmeName = projectName }; await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); @@ -229,6 +236,16 @@ namespace Tgstation.Server.Host.Components using (repository) await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); + if (job.DmeName == null) + { + job.DmeName = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); + if (job.DmeName == default) + { + job.Output = "Unable to find any .dme!"; + return job; + } + } + await ModifyDme(job, cancellationToken).ConfigureAwait(false); //run compiler, verify api @@ -263,18 +280,5 @@ namespace Tgstation.Server.Host.Components throw; } } - - /// - public Task StartAsync(CancellationToken cancellationToken) - { - //THIS IS FOR AUTO COMPILE INTERVAL STUFF, STOP TRYING TO REMOVE IT - throw new NotImplementedException(); - } - - /// - public Task StopAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 3e7c52eca5..215c1582ac 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -8,15 +8,15 @@ namespace Tgstation.Server.Host.Components /// /// For managing the compiler /// - interface IDreamMaker : IHostedService + interface IDreamMaker { /// /// Starts a compile /// - /// The .dme file to use without the extension + /// The name of the .dme to compile without the extension /// The to copy from /// The for the operation /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated - Task Compile(string dmeName, IRepository repository, CancellationToken cancellationToken); + Task Compile(string projectName, IRepository repository, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 3735a5cc13..8105873fc5 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Hosting; +using System.Threading.Tasks; namespace Tgstation.Server.Host.Components { @@ -19,5 +20,7 @@ namespace Tgstation.Server.Host.Components Api.Models.Instance GetMetadata(); void Rename(string newName); + + Task SetAutoUpdateInterval(int? newInterval); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index 7dfd5a667d..5e4e3934f8 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { @@ -11,8 +11,8 @@ namespace Tgstation.Server.Host.Components /// Create an /// /// The - /// The for the operation + /// The for the operation /// A new - IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext); + IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContextFactory databaseContextFactory); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs index f778e46798..f7db73a8d7 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -7,8 +7,8 @@ namespace Tgstation.Server.Host.Components /// /// Factory for creating and loading s /// - interface IRepositoryManager : IHostedService - { + interface IRepositoryManager + { /// /// Attempt to load the from the default location /// @@ -24,12 +24,5 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// The newly cloned Task CloneRepository(string url, string accessString, CancellationToken cancellationToken); - - /// - /// Change the interval in minutes at which the repository auto updates - /// - /// The new interval in minutes or to disable the auto update - /// A representing the running operation - Task SetAutoUpdateInterval(int? newInterval); } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 475c12e3d4..aea3891519 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,28 +1,58 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { - sealed class Instance : IInstance + /// + sealed class Instance : IInstance, IDisposable { + /// public IRepositoryManager RepositoryManager { get; } + /// public IByond Byond { get; } + /// public IDreamMaker DreamMaker { get; } + /// public IDreamDaemon DreamDaemon { get; } + /// public IChat Chat { get; } + /// public IConfiguration Configuration { get; } + /// + /// The for the + /// readonly ICompileJobConsumer compileJobConsumer; + + /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + /// + /// The for the + /// readonly Api.Models.Instance metadata; - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer) + /// + /// The auto update + /// + Task timerTask; + /// + /// for + /// + CancellationTokenSource timerCts; + + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); @@ -32,10 +62,50 @@ namespace Tgstation.Server.Host.Components Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); } + /// + public void Dispose() => timerCts?.Dispose(); + + /// + /// Pull the repository and compile for every set of given + /// + /// How many minutes the operation should repeat. Does not include running time + /// The for the operation + /// A representing the running operation + async Task TimerLoop(int minutes, CancellationToken cancellationToken) + { + try + { + while (true) + { + await Task.Delay(new TimeSpan(0, minutes, 0), cancellationToken).ConfigureAwait(false); + + string accessToken = null, projectName = null; + var dbTask = databaseContextFactory.UseContext(async (db) => + { + var instanceQuery = db.Instances.Where(x => x.Id == metadata.Id); + var projectNameTask = instanceQuery.Select(x => x.DreamMakerSettings.ProjectName).FirstOrDefaultAsync(cancellationToken); + accessToken = await instanceQuery.Select(x => x.RepositorySettings.AccessToken).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + projectName = await projectNameTask.ConfigureAwait(false); + }); + using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + await dbTask.ConfigureAwait(false); + await repo.FetchOrigin(accessToken, cancellationToken).ConfigureAwait(false); + await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); + await DreamMaker.Compile(projectName, repo, cancellationToken).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException) { } + } + + /// public Api.Models.Instance GetMetadata() => metadata.CloneMetadata(); + /// public void Rename(string newName) { if (String.IsNullOrWhiteSpace(newName)) @@ -43,8 +113,38 @@ namespace Tgstation.Server.Host.Components metadata.Name = newName; } - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), DreamMaker.StartAsync(cancellationToken)); + /// + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)); - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), DreamMaker.StopAsync(cancellationToken)); + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); + + /// + public async Task SetAutoUpdateInterval(int? newInterval) + { + Task toWait; + lock (this) + { + if (timerTask != null) + { + timerCts.Cancel(); + toWait = timerTask; + } + else + toWait = Task.CompletedTask; + } + await toWait.ConfigureAwait(false); + if (!newInterval.HasValue) + return; + lock (this) + { + //race condition, just quit + if (timerTask != null) + return; + timerCts?.Dispose(); + timerCts = new CancellationTokenSource(); + timerTask = TimerLoop(newInterval.Value, timerCts.Token); + } + } } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index ed4955194e..00767fcabd 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -1,6 +1,5 @@ using System; using Tgstation.Server.Host.Core; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { @@ -19,7 +18,7 @@ namespace Tgstation.Server.Host.Components public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); /// - public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext) + public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContextFactory databaseContextFactory) { //Create the ioManager for the instance @@ -32,7 +31,7 @@ namespace Tgstation.Server.Host.Components var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); var codeModificationsIoMananger = new ResolvingIOManager(instanceIoManager, "CodeModifications"); - var dmbFactory = new DmbFactory(databaseContext, gameIoManager); + var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager); diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index 5982887adb..ffb8cf1049 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -25,16 +25,6 @@ namespace Tgstation.Server.Host.Components /// readonly SemaphoreSlim semaphore; - /// - /// for - /// - CancellationTokenSource timerCancellationTokenSource; - - /// - /// Represents the running update timer if any - /// - Task currentTimerTask; - /// /// Construct a /// @@ -48,49 +38,7 @@ namespace Tgstation.Server.Host.Components } /// - public void Dispose() - { - timerCancellationTokenSource?.Dispose(); - semaphore.Dispose(); - } - - /// - /// Stops and joins it - /// - /// A representing the running operation - async Task StopTimer() - { - if (currentTimerTask == null) - return; - timerCancellationTokenSource.Cancel(); - await currentTimerTask.ConfigureAwait(false); - currentTimerTask = null; - } - - /// - /// Asyncronously fetch and reset the current branch for each given amount of - /// - /// The delay of the timer - /// The accessString to use for fetch operations - /// The for the operation - /// A representing the running operation - async Task TimerLoop(int minutes, string accessString, CancellationToken cancellationToken) - { - try - { - while (true) - { - await Task.Delay(TimeSpan.FromMinutes(minutes), cancellationToken).ConfigureAwait(false); - using (var repo = await LoadRepository(cancellationToken).ConfigureAwait(false)) - { - //TODO: Find the unauthorized exception, catch it, and log it - await repo.FetchOrigin(accessString, cancellationToken).ConfigureAwait(false); - await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); - } - } - } - catch (OperationCanceledException) { } - } + public void Dispose() => semaphore.Dispose(); /// public async Task CloneRepository(string url, string accessString, CancellationToken cancellationToken) @@ -134,33 +82,5 @@ namespace Tgstation.Server.Host.Components localSemaphore = null; }); } - - /// - public async Task SetAutoUpdateInterval(int? newInterval) - { - await StopTimer().ConfigureAwait(false); - if (!newInterval.HasValue) - return; - - string accessString = null; - - if (timerCancellationTokenSource != null) - timerCancellationTokenSource.Dispose(); - timerCancellationTokenSource = new CancellationTokenSource(); - - currentTimerTask = TimerLoop(repositorySettings.AutoUpdateInterval.Value, accessString, timerCancellationTokenSource.Token); - } - - /// - public Task StartAsync(CancellationToken cancellationToken) => SetAutoUpdateInterval(repositorySettings.AutoUpdateInterval); - - /// - public async Task StopAsync(CancellationToken cancellationToken) - { - var timerStopTask = StopTimer(); - var tcs = new TaskCompletionSource(); - using (cancellationToken.Register(() => tcs.SetCanceled())) - await Task.WhenAny(timerStopTask, tcs.Task).ConfigureAwait(false); - } } } diff --git a/src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs b/src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs new file mode 100644 index 0000000000..305092416c --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Factory for scoping usage of s. Meant for use by + /// + interface IDatabaseContextFactory + { + /// + /// Run an in the scope of an + /// + /// The operation to run + /// A representing the running + Task UseContext(Func operation); + } +} From 6ad70948daa563799d1c416d8840a24bc183e588 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 10:10:56 -0400 Subject: [PATCH 143/397] Implement DatabaseContextFactory --- .../Core/DatabaseContextFactory.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs diff --git a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs new file mode 100644 index 0000000000..f3d64f355c --- /dev/null +++ b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class DatabaseContextFactory : IDatabaseContextFactory + { + /// + /// The for the + /// + readonly IServiceProvider serviceProvider; + + /// + /// Construct a + /// + /// The value of + public DatabaseContextFactory(IServiceProvider serviceProvider) => this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + + /// + public async Task UseContext(Func operation) + { + using (var scope = serviceProvider.CreateScope()) + await operation(scope.ServiceProvider.GetRequiredService()).ConfigureAwait(false); + } + } +} From 3e9a8475b1c3eb767ce5b84dc7a8a1714afa387f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 10:27:49 -0400 Subject: [PATCH 144/397] It all comes together --- .../Components/IInstanceFactory.cs | 3 +- .../Components/IInstanceManager.cs | 6 +-- .../Components/IRepository.cs | 2 +- .../Components/IRepositoryManager.cs | 3 +- .../Components/InstanceFactory.cs | 14 +++++- .../Components/InstanceManager.cs | 47 +++++++++---------- .../Controllers/DreamMakerController.cs | 22 +++++++-- .../Models/DatabaseContext.cs | 7 ++- .../Models/IDatabaseContext.cs | 5 ++ 9 files changed, 64 insertions(+), 45 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index 5e4e3934f8..d9d4f4cda8 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -11,8 +11,7 @@ namespace Tgstation.Server.Host.Components /// Create an /// /// The - /// The for the operation /// A new - IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContextFactory databaseContextFactory); + IInstance CreateInstance(Host.Models.Instance metadata); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index c087ad362a..d26d87489b 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -20,10 +20,9 @@ namespace Tgstation.Server.Host.Components /// Online an /// /// The of the desired - /// The for the operation /// The for the operation /// A representing the running operation - Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken); + Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken); /// /// Offline an @@ -37,10 +36,9 @@ namespace Tgstation.Server.Host.Components /// Move an /// /// The of the desired - /// The for the operation /// The new path of the . will have this set on if the operation completes successfully /// The for the operation /// A representing the running operation - Task MoveInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken); + Task MoveInstance(Host.Models.Instance metadata, string newPath, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/IRepository.cs index 7acd5053df..09acbb72c5 100644 --- a/src/Tgstation.Server.Host/Components/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/IRepository.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components /// /// Represents an on-disk git repository /// - interface IRepository : IDisposable + public interface IRepository : IDisposable { /// /// If the was cloned from GitHub.com diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs index f7db73a8d7..5f39b45733 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Hosting; -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 00767fcabd..62112fac9b 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -11,14 +11,24 @@ namespace Tgstation.Server.Host.Components /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + /// /// Construct an /// /// The value of - public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + } /// - public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContextFactory databaseContextFactory) + public IInstance CreateInstance(Host.Models.Instance metadata) { //Create the ioManager for the instance diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 1b01e2b8e9..3d1a615f2c 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Core; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { @@ -16,18 +15,18 @@ namespace Tgstation.Server.Host.Components sealed class InstanceManager : IInstanceManager, IHostedService { /// - /// The for the + /// The for the /// readonly IInstanceFactory instanceFactory; /// - /// The for the - /// - readonly IServiceProvider serviceProvider; - /// - /// The for the + /// The for the /// readonly IIOManager ioManager; /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + /// /// Map of s to respective s /// readonly Dictionary instances; @@ -36,13 +35,13 @@ namespace Tgstation.Server.Host.Components /// Construct an /// /// The value of - /// The value of /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IServiceProvider serviceProvider, IIOManager ioManager) + /// The value of + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); - this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); instances = new Dictionary(); } @@ -60,7 +59,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task MoveInstance(Host.Models.Instance instance, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken) + public async Task MoveInstance(Host.Models.Instance instance, string newPath, CancellationToken cancellationToken) { if (newPath == null) throw new ArgumentNullException(nameof(newPath)); @@ -72,14 +71,14 @@ namespace Tgstation.Server.Host.Components var oldPath = instance.Path; await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); instance.Path = ioManager.ResolvePath(newPath); - instanceOnlineTask = OnlineInstance(instance, databaseContext, default); + instanceOnlineTask = OnlineInstance(instance, default); await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); } finally { if (instance.Online) if (instanceOnlineTask == null) - await OnlineInstance(instance, databaseContext, default).ConfigureAwait(false); + await OnlineInstance(instance, default).ConfigureAwait(false); else await instanceOnlineTask.ConfigureAwait(false); } @@ -101,11 +100,11 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken) + public async Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); - var instance = instanceFactory.CreateInstance(metadata, databaseContext); + var instance = instanceFactory.CreateInstance(metadata); lock (this) { if (instances.ContainsKey(metadata.Id)) @@ -116,18 +115,14 @@ namespace Tgstation.Server.Host.Components } /// - public async Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext => { - using(var scope = serviceProvider.CreateScope()) - { - var databaseContext = scope.ServiceProvider.GetRequiredService(); - await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); - var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); - var tasks = new List(); - await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, databaseContext, cancellationToken)), cancellationToken).ConfigureAwait(false); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - } + await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); + var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); + var tasks = new List(); + await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); + }); /// public async Task StopAsync(CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 56586f7bfd..fd39fdc931 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Controllers /// Controller for managing the compiler /// [Route("/DreamMaker")] - public sealed class DreamMakerController : ModelController + public sealed class DreamMakerController : ModelController { /// /// The for the @@ -38,7 +38,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.Compile)] - public override async Task Create([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken) + public override async Task Create([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) { var job = new Job { @@ -53,16 +53,30 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.CancelCompile)] - public override async Task Delete([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken) + public override async Task Delete([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) { //alias for cancelling the latest job - var job = await DatabaseContext.Jobs.OrderByDescending(x => x.StartedAt).Select(x => new Job { Id = x.Id, StoppedAt = x.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); + var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Select(x => new Job { Id = x.Job.Id, StoppedAt = x.Job.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); if (job.StoppedAt != null) return StatusCode(HttpStatusCode.Gone); jobManager.CancelJob(job); return Ok(); } + /// + [TgsAuthorize(DreamMakerRights.SetDme)] + public override async Task Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) + { + var hostModel = new DreamMakerSettings + { + InstanceId = Instance.Id + }; + DatabaseContext.DreamMakerSettings.Attach(hostModel); + hostModel.ProjectName = model.ProjectName; + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + return Ok(); + } + /// /// Run the compile job and insert it into the database /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 3d848add3e..b97991c636 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -28,6 +28,9 @@ namespace Tgstation.Server.Host.Models /// public DbSet RevisionInformations { get; set; } + /// + public DbSet DreamMakerSettings { get; set; } + /// /// The for s /// @@ -50,10 +53,6 @@ namespace Tgstation.Server.Host.Models /// public DbSet DreamDaemonSettings { get; set; } /// - /// The in the - /// - public DbSet DreamMakerSettings { get; set; } - /// /// The s in the /// public DbSet Jobs { get; set; } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index da1bea8f12..d027884bc4 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -34,6 +34,11 @@ namespace Tgstation.Server.Host.Models /// DbSet RevisionInformations { get; } + /// + /// The in the + /// + DbSet DreamMakerSettings { get; set; } + /// /// Get the in the /// From 18304741c52572c2a654f1abfb7ceca4f5f6c875 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 11:15:45 -0400 Subject: [PATCH 145/397] Pull the chains --- .../Models/CompilerStatus.cs | 26 +++- src/Tgstation.Server.Api/Models/Job.cs | 6 - .../Components/DreamMaker.cs | 136 +++++++++++------- .../Components/IByond.cs | 2 +- src/Tgstation.Server.Host/Components/IChat.cs | 10 +- .../Components/IConfiguration.cs | 2 +- .../Components/IDreamDaemon.cs | 2 +- .../Components/IDreamMaker.cs | 7 +- .../Components/IInstance.cs | 36 ++++- .../Components/IInstanceManager.cs | 3 +- .../Components/IInstanceShutdownHandler.cs | 14 +- .../Components/IRepositoryManager.cs | 9 +- .../Components/RepositoryManager.cs | 4 +- .../Components/ServerSideModifications.cs | 2 +- .../Controllers/DreamMakerController.cs | 27 +++- src/Tgstation.Server.Host/Core/IJobManager.cs | 5 +- src/Tgstation.Server.Host/Core/JobManager.cs | 14 +- .../Models/CompileJob.cs | 20 ++- .../Models/IApiConvertable.cs | 15 ++ src/Tgstation.Server.Host/Models/Job.cs | 22 ++- .../Models/RevisionInformation.cs | 11 +- src/Tgstation.Server.Host/Models/TestMerge.cs | 18 ++- src/Tgstation.Server.Host/Models/User.cs | 23 ++- 23 files changed, 321 insertions(+), 93 deletions(-) create mode 100644 src/Tgstation.Server.Host/Models/IApiConvertable.cs diff --git a/src/Tgstation.Server.Api/Models/CompilerStatus.cs b/src/Tgstation.Server.Api/Models/CompilerStatus.cs index 8ca0b48143..d29df195e6 100644 --- a/src/Tgstation.Server.Api/Models/CompilerStatus.cs +++ b/src/Tgstation.Server.Api/Models/CompilerStatus.cs @@ -12,16 +12,32 @@ /// Idle, /// - /// The directiory is being targeted - /// - Targeting, - /// /// The is being copied /// Copying, + /// + /// The .dme is having it's server side modifications applied + /// + Modifying, /// /// DreamMaker is running /// - Compiling + Compiling, + /// + /// The DMAPI is being verified + /// + Verifying, + /// + /// The compile results are being duplicated + /// + Duplicating, + /// + /// The configuration is being linked to the compile results + /// + Symlinking, + /// + /// A failed compile job is being erased + /// + Cleanup } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs index b1141f3400..2f413fff43 100644 --- a/src/Tgstation.Server.Api/Models/Job.cs +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -5,12 +5,6 @@ /// public sealed class Job : Internal.Job { - /// - /// If the has incremental progress, this will range from 1 - 100. 0 otherwise - /// - [Permissions(DenyWrite = true)] - public int Progress { get; set; } - /// /// The that started the job /// diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index f70df87e5e..ffd577f98b 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -33,6 +33,9 @@ namespace Tgstation.Server.Host.Components /// const string DmeExtension = ".dme"; + /// + public CompilerStatus Status { get; private set; } + /// /// The for /// @@ -172,6 +175,12 @@ namespace Tgstation.Server.Host.Components } } + /// + /// Adds server side includes to the .dme being compiled + /// + /// The for the operation + /// The for the operation + /// A representing the running operation async Task ModifyDme(Host.Models.CompileJob job, CancellationToken cancellationToken) { var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); @@ -211,73 +220,92 @@ namespace Tgstation.Server.Host.Components /// public async Task Compile(string projectName, IRepository repository, CancellationToken cancellationToken) { - var job = new Host.Models.CompileJob - { - DirectoryName = Guid.NewGuid(), - DmeName = projectName - }; - await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); - var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName); - - async Task CleanupFailedCompile() - { - try - { - await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); - } - catch { } - }; - try { - //copy the repository - var fullDirA = ioManager.ResolvePath(dirA); - using (repository) - await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - - if (job.DmeName == null) + Status = CompilerStatus.Copying; + var job = new Host.Models.CompileJob { - job.DmeName = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); - if (job.DmeName == default) + DirectoryName = Guid.NewGuid(), + DmeName = projectName + }; + await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName); + + async Task CleanupFailedCompile() + { + Status = CompilerStatus.Cleanup; + try { - job.Output = "Unable to find any .dme!"; - return job; + await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); } - } + catch { } + }; - await ModifyDme(job, cancellationToken).ConfigureAwait(false); - - //run compiler, verify api - var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + try { - await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + //copy the repository + var fullDirA = ioManager.ResolvePath(dirA); + using (repository) + await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); - }, true).ConfigureAwait(false); + Status = CompilerStatus.Modifying; - if (!ddVerified) - //server never validated or compile failed + if (job.DmeName == null) + { + job.DmeName = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); + if (job.DmeName == default) + { + job.Output = "Unable to find any .dme!"; + return job; + } + } + + await ModifyDme(job, cancellationToken).ConfigureAwait(false); + + Status = CompilerStatus.Compiling; + + //run compiler, verify api + var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + { + await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + + Status = CompilerStatus.Verifying; + + return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); + }, true).ConfigureAwait(false); + + if (!ddVerified) + //server never validated or compile failed + await CleanupFailedCompile().ConfigureAwait(false); + else + { + job.DMApiValidated = true; + + Status = CompilerStatus.Duplicating; + + //duplicate the dmb et al + await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); + + Status = CompilerStatus.Symlinking; + + //symlink in the static data + var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); + await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); + await symATask.ConfigureAwait(false); + } + compileJobConsumer.LoadCompileJob(job); + return job; + } + catch + { await CleanupFailedCompile().ConfigureAwait(false); - else - { - job.DMApiValidated = true; - - //duplicate the dmb et al - await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); - - //symlink in the static data - var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); - await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); - await symATask.ConfigureAwait(false); + throw; } - compileJobConsumer.LoadCompileJob(job); - return job; } - catch + finally { - await CleanupFailedCompile().ConfigureAwait(false); - throw; + Status = CompilerStatus.Idle; } } } diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByond.cs index acf4f4c7cc..6af7e4627b 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByond.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing the BYOND installation /// - interface IByond + public interface IByond { /// /// Change the current BYOND version diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/IChat.cs index bd43137bca..a1f494a1ff 100644 --- a/src/Tgstation.Server.Host/Components/IChat.cs +++ b/src/Tgstation.Server.Host/Components/IChat.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing connected chat services /// - interface IChat : IHostedService + public interface IChat : IHostedService { /// /// If the IRC client is connected @@ -47,6 +47,14 @@ namespace Tgstation.Server.Host.Components /// A representing the running operation Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken); + /// + /// Start tracking json files for commands and channels + /// + /// The base path of the .jsons + /// The name of the chat channels json + /// The name of the chat commands json + /// The for the operation + /// A resulting in a tied to the lifetime of the json trackings Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index 3b31dda93e..da32288f4d 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing the Configuration directory /// - interface IConfiguration + public interface IConfiguration { /// /// Copies all files in the CodeModifications directory to diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index b052ce9ead..dbd32b9d2a 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing DreamDaemon /// - interface IDreamDaemon : IHostedService + public interface IDreamDaemon : IHostedService { /// /// If DreamDaemon is running diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 215c1582ac..938aeb46b8 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -8,8 +8,13 @@ namespace Tgstation.Server.Host.Components /// /// For managing the compiler /// - interface IDreamMaker + public interface IDreamMaker { + /// + /// The of + /// + Api.Models.CompilerStatus Status { get; } + /// /// Starts a compile /// diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 8105873fc5..a06c30c3a3 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -3,24 +3,58 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components { - interface IInstance : IHostedService + /// + /// For interacting with the instance services + /// + public interface IInstance : IHostedService { + /// + /// The for the + /// IRepositoryManager RepositoryManager { get; } + /// + /// The for the + /// IByond Byond { get; } + /// + /// The for the + /// IDreamMaker DreamMaker { get; } + /// + /// The for the + /// IDreamDaemon DreamDaemon { get; } + /// + /// The for the + /// IChat Chat { get; } + /// + /// The for the + /// IConfiguration Configuration { get; } + /// + /// Get the associated with the + /// + /// The associated with the Api.Models.Instance GetMetadata(); + /// + /// Rename the + /// + /// The new name for the void Rename(string newName); + /// + /// Change the for the + /// + /// The new auto update inteval + /// A representing the running operation Task SetAutoUpdateInterval(int? newInterval); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index d26d87489b..68a60ad247 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -1,13 +1,12 @@ using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { /// /// For managing s /// - interface IInstanceManager : IInstanceShutdownHandler + public interface IInstanceManager : IInstanceShutdownHandler { /// /// Get the associated with given diff --git a/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs b/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs index e07de394b6..87264ca110 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs @@ -3,8 +3,20 @@ using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components { - interface IInstanceShutdownHandler + /// + /// For handling shutdowns + /// + public interface IInstanceShutdownHandler { + //TODO + /// + /// OMG + /// + /// + /// + /// + /// + /// Task PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary); } } diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs index 5f39b45733..94fd3a2ac3 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components @@ -6,7 +7,7 @@ namespace Tgstation.Server.Host.Components /// /// Factory for creating and loading s /// - interface IRepositoryManager + public interface IRepositoryManager { /// /// Attempt to load the from the default location @@ -18,10 +19,10 @@ namespace Tgstation.Server.Host.Components /// /// Delete the current and replaces it with a clone of the repository at /// - /// The location of the remote repository to clone + /// The of the remote repository to clone /// The access string to clone from /// The for the operation /// The newly cloned - Task CloneRepository(string url, string accessString, CancellationToken cancellationToken); + Task CloneRepository(Uri url, string accessString, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index ffb8cf1049..e87d417cbd 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -41,7 +41,7 @@ namespace Tgstation.Server.Host.Components public void Dispose() => semaphore.Dispose(); /// - public async Task CloneRepository(string url, string accessString, CancellationToken cancellationToken) + public async Task CloneRepository(Uri url, string accessString, CancellationToken cancellationToken) { await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false); @@ -50,7 +50,7 @@ namespace Tgstation.Server.Host.Components string path = null; try { - path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url, accessString), ioManager.ResolvePath("."), new CloneOptions + path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url.ToString(), accessString), ioManager.ResolvePath("."), new CloneOptions { OnProgress = (a) => !cancellationToken.IsCancellationRequested, OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, diff --git a/src/Tgstation.Server.Host/Components/ServerSideModifications.cs b/src/Tgstation.Server.Host/Components/ServerSideModifications.cs index a9edcb1ddc..e54faac733 100644 --- a/src/Tgstation.Server.Host/Components/ServerSideModifications.cs +++ b/src/Tgstation.Server.Host/Components/ServerSideModifications.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components /// /// Represents code modifications via configuration /// - sealed class ServerSideModifications + public sealed class ServerSideModifications { /// /// If the target dme was completely overwitten diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index fd39fdc931..f94a51537e 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -24,6 +24,10 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IInstanceManager instanceManager; /// /// Construct a @@ -31,9 +35,26 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) + /// The value of + public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + + /// + [TgsAuthorize(DreamMakerRights.Read)] + public override async Task Read(CancellationToken cancellationToken) + { + var instance = instanceManager.GetInstance(Instance); + var projectNameTask = DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).Select(x => x.ProjectName).FirstAsync(cancellationToken); + var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Include(x => x.Job).FirstAsync(cancellationToken).ConfigureAwait(false); + return Json(new Api.Models.DreamMaker + { + LastJob = job.ToApi(), + ProjectName = await projectNameTask.ConfigureAwait(false), + Status = instance.DreamMaker.Status + }); } /// @@ -59,7 +80,7 @@ namespace Tgstation.Server.Host.Controllers var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Select(x => new Job { Id = x.Job.Id, StoppedAt = x.Job.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); if (job.StoppedAt != null) return StatusCode(HttpStatusCode.Gone); - jobManager.CancelJob(job); + await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } @@ -90,7 +111,7 @@ namespace Tgstation.Server.Host.Controllers var instanceManager = serviceProvider.GetRequiredService(); var databaseContext = serviceProvider.GetRequiredService(); - var projectName = await databaseContext.Instances.Where(x => x.Id == instanceModel.Id).Select(x => x.DreamMakerSettings.ProjectName).FirstAsync(cancellationToken).ConfigureAwait(false); + var projectName = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => x.ProjectName).FirstAsync(cancellationToken).ConfigureAwait(false); var instance = instanceManager.GetInstance(instanceModel); diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 0912a64890..f3bca3dd91 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -32,6 +32,9 @@ namespace Tgstation.Server.Host.Core /// Cancels a give /// /// The to cancel - void CancelJob(Job job); + /// The who cancelled the + /// The for the operation + /// A representing a running operation + Task CancelJob(Job job, User user, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 0676f2cc17..0c6e340011 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -145,6 +145,18 @@ namespace Tgstation.Server.Host.Core } /// - public void CancelJob(Job job) => CheckGetJob(job).Cancel(); + public async Task CancelJob(Job job, User user, CancellationToken cancellationToken) + { + if (user != null) + using (var scope = serviceProvider.CreateScope()) + { + var databaseContext = scope.ServiceProvider.GetRequiredService(); + job = new Job { Id = job.Id }; + databaseContext.Jobs.Attach(job); + job.CancelledBy = user; + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } + CheckGetJob(job).Cancel(); + } } } diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index f15efd5859..6245765298 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class CompileJob : Api.Models.Internal.CompileJob - { + public sealed class CompileJob : Api.Models.Internal.CompileJob, IApiConvertable + { /// /// The of /// @@ -21,5 +22,18 @@ namespace Tgstation.Server.Host.Models /// [Required] public RevisionInformation RevisionInformation { get; set; } - } + + /// + public Api.Models.CompileJob ToApi() => new Api.Models.CompileJob + { + DirectoryName = DirectoryName, + DMApiValidated = DMApiValidated, + DmeName = DmeName, + ExitCode = ExitCode, + Id = Id, + Job = Job.ToApi(), + Output = Output, + RevisionInformation = RevisionInformation.ToApi() + }; + } } diff --git a/src/Tgstation.Server.Host/Models/IApiConvertable.cs b/src/Tgstation.Server.Host/Models/IApiConvertable.cs new file mode 100644 index 0000000000..3a53bb26c7 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/IApiConvertable.cs @@ -0,0 +1,15 @@ +namespace Tgstation.Server.Host.Models +{ + /// + /// For converting models to their API form + /// + /// Which of the this model backs + public interface IApiConvertable where TModel : class + { + /// + /// Convert the model to it's API form + /// + /// A new + TModel ToApi(); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 43d1fefba4..51a4b5d50c 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -3,12 +3,32 @@ namespace Tgstation.Server.Host.Models { /// - public sealed class Job : Api.Models.Internal.Job + public sealed class Job : Api.Models.Internal.Job, IApiConvertable { /// /// See /// [Required] public User StartedBy { get; set; } + + /// + /// See + /// + public User CancelledBy { get; set; } + + /// + public Api.Models.Job ToApi() => new Api.Models.Job + { + Id = Id, + StartedAt = StartedAt, + StoppedAt = StoppedAt, + Cancelled = Cancelled, + CancelledBy = CancelledBy.ToApi(), + CancelRight = CancelRight, + CancelRightsType = CancelRightsType, + Description = Description, + ExceptionDetails = ExceptionDetails, + StartedBy = StartedBy.ToApi() + }; } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 057cc7e680..2d05122e66 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using System.Linq; namespace Tgstation.Server.Host.Models { /// - public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation + public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation, IApiConvertable { /// /// The row Id @@ -14,5 +15,13 @@ namespace Tgstation.Server.Host.Models /// See /// public List TestMerges { get; set; } + + /// + public Api.Models.RevisionInformation ToApi() => new Api.Models.RevisionInformation + { + Commit = Commit, + OriginRevision = OriginRevision, + TestMerges = TestMerges.Select(x => x.ToApi()).ToList() + }; } } diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index 05e201c04b..108f584086 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class TestMerge : Api.Models.Internal.TestMerge + public sealed class TestMerge : Api.Models.Internal.TestMerge, IApiConvertable { /// /// See @@ -15,5 +16,20 @@ namespace Tgstation.Server.Host.Models /// The for the /// public RevisionInformation RevisionInformation { get; set; } + + /// + public Api.Models.TestMerge ToApi() => new Api.Models.TestMerge + { + Author = Author, + BodyAtMerge = BodyAtMerge, + MergedAt = MergedAt, + TitleAtMerge = TitleAtMerge, + Comment = Comment, + Id = Id, + MergedBy = MergedBy.ToApi(), + Number =Number, + PullRequestRevision = PullRequestRevision, + Url = Url + }; } } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index dec14852f9..93587647ef 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class User : Api.Models.Internal.User + public sealed class User : Api.Models.Internal.User, IApiConvertable { /// /// The hash of the user's password @@ -24,5 +25,25 @@ namespace Tgstation.Server.Host.Models /// The s for the /// public List InstanceUsers { get; set; } + + /// + /// See + /// + /// If we should recurse on + /// A new + Api.Models.User ToApi(bool recursive) => new Api.Models.User + { + AdministrationRights = AdministrationRights, + CreatedAt = CreatedAt, + CreatedBy = recursive ? CreatedBy?.ToApi(false) : null, + Enabled = Enabled, + Id = Id, + InstanceManagerRights = InstanceManagerRights, + Name = Name, + SystemIdentifier = SystemIdentifier + }; + + /// + public Api.Models.User ToApi() => ToApi(true); } } From 64dc20e556a203b9f08f259e12fdccaa2978f28a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 11:50:00 -0400 Subject: [PATCH 146/397] That's the jobs controller --- .../Controllers/JobController.cs | 70 +++++++++++++++++++ src/Tgstation.Server.Host/Core/IJobManager.cs | 8 --- src/Tgstation.Server.Host/Core/JobManager.cs | 56 ++++++++------- .../Models/InstanceUser.cs | 10 +++ src/Tgstation.Server.Host/Models/Job.cs | 5 ++ 5 files changed, 114 insertions(+), 35 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/JobController.cs diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs new file mode 100644 index 0000000000..982fc72cd8 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// for s + /// + [Route("/Job")] + public sealed class JobController : ModelController + { + /// + /// The for the + /// + readonly IJobManager jobManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) + { + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + } + + /// + [TgsAuthorize] + public override async Task List(CancellationToken cancellationToken) + { + IQueryable query = DatabaseContext.Jobs; + if (Instance != null) + { + if (!AuthenticationContext.InstanceUser.AnyRights) + return Forbid(); + query = query.Where(x => x.Instance.Id == Instance.Id); + } + else + query = query.Where(x => x.Instance == null); + + var jobs = await query.Where(x => x.StoppedAt == null).ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(jobs.Select(x => x.ToApi())); + } + + /// + [TgsAuthorize] + public override async Task Delete([FromBody] Api.Models.Job model, CancellationToken cancellationToken) + { + //don't care if an instance post or not at this point + var job = await DatabaseContext.Jobs.Where(x => x.Id == model.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (job == default(Job)) + return NotFound(); + + if(job.StoppedAt != null) + return StatusCode(HttpStatusCode.Gone); + + await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); + return Ok(); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index f3bca3dd91..3eabf70612 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -20,14 +20,6 @@ namespace Tgstation.Server.Host.Core /// A representing a running operation Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken); - /// - /// Wait for a given to complete - /// - /// The to wait for - /// The for the operation - /// A representing a running operation - Task WaitForJob(Job job, CancellationToken cancellationToken); - /// /// Cancels a give /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 0c6e340011..03f12ef437 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -55,33 +55,45 @@ namespace Tgstation.Server.Host.Core /// A representing the running operation async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) { - using (var scope = serviceProvider.CreateScope()) + try { - IDatabaseContext databaseContext = null; - try + using (var scope = serviceProvider.CreateScope()) { - var oldJob = job; - job = new Job { Id = oldJob.Id }; + IDatabaseContext databaseContext = null; try { - await operation(job, scope.ServiceProvider, cancellationToken).ConfigureAwait(false); + var oldJob = job; + job = new Job { Id = oldJob.Id }; + try + { + await operation(job, scope.ServiceProvider, cancellationToken).ConfigureAwait(false); + } + finally + { + databaseContext = scope.ServiceProvider.GetRequiredService(); + databaseContext.Jobs.Attach(job); + } } - finally + catch (OperationCanceledException) { - databaseContext = scope.ServiceProvider.GetRequiredService(); - databaseContext.Jobs.Attach(job); + job.Cancelled = true; } + catch (Exception e) + { + job.ExceptionDetails = e.ToString(); + } + job.StoppedAt = DateTimeOffset.Now; + await databaseContext.Save(default).ConfigureAwait(false); } - catch (OperationCanceledException) + } + finally + { + lock (this) { - job.Cancelled = true; + var handler = jobs[job.Id]; + jobs.Remove(job.Id); + handler.Dispose(); } - catch (Exception e) - { - job.ExceptionDetails = e.ToString(); - } - job.StoppedAt = DateTimeOffset.Now; - await databaseContext.Save(default).ConfigureAwait(false); } } @@ -134,16 +146,6 @@ namespace Tgstation.Server.Host.Core jobs.Clear(); } - /// - public async Task WaitForJob(Job job, CancellationToken cancellationToken) - { - var handler = CheckGetJob(job); - await handler.Wait(cancellationToken).ConfigureAwait(false); - lock (this) - jobs.Remove(job.Id); - handler.Dispose(); - } - /// public async Task CancelJob(Job job, User user, CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 6566b2c891..8e2f3b9ec5 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Host.Models { @@ -15,5 +16,14 @@ namespace Tgstation.Server.Host.Models /// [Required] public Instance Instance { get; set; } + + /// + /// If the has any instance rights + /// + public bool AnyRights => ByondRights != ByondRights.None || + ChatSettingsRights != ChatSettingsRights.None || + ConfigurationRights != ConfigurationRights.None || + DreamDaemonRights != DreamDaemonRights.None || + DreamMakerRights != DreamMakerRights.None; } } diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 51a4b5d50c..1c0cb88d3c 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -16,6 +16,11 @@ namespace Tgstation.Server.Host.Models /// public User CancelledBy { get; set; } + /// + /// The the job belongs to if any + /// + public Instance Instance { get; set; } + /// public Api.Models.Job ToApi() => new Api.Models.Job { From f9ba711bc2b336fd614d4bf762dbe019f1ff3931 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 14:11:00 -0400 Subject: [PATCH 147/397] Heavy WIP --- .../Internal/DreamDaemonLaunchParameters.cs | 15 ++- .../Models/Internal/DreamDaemonSettings.cs | 12 +- .../Components/IDreamDaemon.cs | 4 + .../Controllers/DreamDaemonController.cs | 121 ++++++++++++++++++ .../Controllers/DreamMakerController.cs | 7 +- .../Controllers/JobController.cs | 14 +- .../Controllers/ModelController.cs | 8 ++ 7 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index 8602b302b5..62e0f7eb87 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -12,24 +13,28 @@ namespace Tgstation.Server.Api.Models.Internal /// If the BYOND web client can be used to connect to the game server /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] - public bool AllowWebClient { get; set; } + [Required] + public bool? AllowWebClient { get; set; } /// /// The level of /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] - public DreamDaemonSecurity SecurityLevel { get; set; } + [Required] + public DreamDaemonSecurity? SecurityLevel { get; set; } /// /// The first port uses. This should be the publically advertised port /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort PrimaryPort { get; set; } + [Required] + public ushort? PrimaryPort { get; set; } /// /// The second port uses /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort SecondaryPort { get; set; } + [Required] + public ushort? SecondaryPort { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index b4fd279881..72260ee5fc 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -11,18 +12,21 @@ namespace Tgstation.Server.Api.Models.Internal /// If starts when it's starts /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] - public bool AutoStart { get; set; } + [Required] + public bool? AutoStart { get; set; } /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)] - public bool SoftRestart { get; set; } + [Required] + public bool? SoftRestart { get; set; } /// /// If the server is undergoing a soft shutdown /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] - public bool SoftShutdown { get; set; } + [Required] + public bool? SoftShutdown { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index dbd32b9d2a..ae7dee5038 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -41,6 +41,10 @@ namespace Tgstation.Server.Host.Components /// string AccessToken { get; } + DreamDaemonLaunchParameters LastLaunchParameters { get; } + + Host.Models.CompileJob LastCompileJob { get; } + /// /// Launch DreamDaemon /// diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs new file mode 100644 index 0000000000..d8f082abb6 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -0,0 +1,121 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + [Route("/" + nameof(DreamDaemon))] + public sealed class DreamDaemonController : ModelController + { + /// + /// The for the + /// + readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) + { + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + + /// + [TgsAuthorize(DreamDaemonRights.Start)] + public override async Task Create([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var instance = instanceManager.GetInstance(Instance); + + if (instance.DreamDaemon.Running) + return StatusCode(HttpStatusCode.Gone); + + var launchParams = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => new DreamDaemonLaunchParameters + { + AllowWebClient = x.DreamDaemonSettings.AllowWebClient, + PrimaryPort = x.DreamDaemonSettings.PrimaryPort, + SecondaryPort = x.DreamDaemonSettings.SecondaryPort, + SecurityLevel = x.DreamDaemonSettings.SecurityLevel + }).FirstAsync(cancellationToken).ConfigureAwait(false); + + await jobManager.RegisterOperation(new Models.Job + { + Description = "Launch DreamDaemon", + CancelRight = (int)DreamDaemonRights.Shutdown, + CancelRightsType = RightsType.DreamDaemon, + Instance = Instance, + StartedBy = AuthenticationContext.User + }, (job, serviceProvider, innerCt) => instance.DreamDaemon.Launch(launchParams, innerCt), cancellationToken).ConfigureAwait(false); + return Ok(); + } + + /// + [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)] + public override async Task Read(CancellationToken cancellationToken) + { + var dd = instanceManager.GetInstance(Instance).DreamDaemon; + + var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadMetadata) != 0; + var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadRevision) != 0; + + var settings = metadata ? await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false) : null; + Api.Models.DreamDaemon result = new Api.Models.DreamDaemon(); + if(metadata) + { + result.AutoStart = settings.AutoStart; + result.CurrentPort = dd.CurrentPort; + result.CurrentSecurity = dd.CurrentSecurity; + result.PrimaryPort = dd.LastLaunchParameters.PrimaryPort; + result.AllowWebClient = dd.LastLaunchParameters.AllowWebClient; + result.Running = dd.Running; + result.SecondaryPort = dd.LastLaunchParameters.SecondaryPort; + result.SecurityLevel = dd.LastLaunchParameters.SecurityLevel; + result.SoftRestart = dd.SoftRebooting; + result.SoftShutdown = dd.SoftStopping; + }; + if (revision) + result.CompileJob = dd.LastCompileJob.ToApi(); + + return Json(result); + } + + /// + [TgsAuthorize(DreamDaemonRights.Shutdown)] + public override async Task Delete([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var instance = instanceManager.GetInstance(Instance); + + if (!instance.DreamDaemon.Running) + return StatusCode(HttpStatusCode.Gone); + + await instance.DreamDaemon.Terminate(false, cancellationToken).ConfigureAwait(false); + return Ok(); + } + + [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown)] + public override async Task Update([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); + + var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon) + if() + } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index f94a51537e..c13cb31e11 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Controller for managing the compiler /// - [Route("/DreamMaker")] + [Route("/" + nameof(DreamMaker))] public sealed class DreamMakerController : ModelController { /// @@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.Controllers readonly IInstanceManager instanceManager; /// - /// Construct a + /// Construct a /// /// The for the /// The for the @@ -66,7 +66,8 @@ namespace Tgstation.Server.Host.Controllers Description = "Compile active repository code", StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.DreamMaker, - CancelRight = (int)DreamMakerRights.CancelCompile + CancelRight = (int)DreamMakerRights.CancelCompile, + Instance = Instance }; await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); return Json(job); diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 982fc72cd8..4be80670a2 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Controllers /// /// for s /// - [Route("/Job")] + [Route("/" + nameof(Job))] public sealed class JobController : ModelController { /// @@ -60,11 +60,23 @@ namespace Tgstation.Server.Host.Controllers if (job == default(Job)) return NotFound(); + if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0) + return Forbid(); + if(job.StoppedAt != null) return StatusCode(HttpStatusCode.Gone); await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } + + [TgsAuthorize] + public override async Task GetId(long id, CancellationToken cancellationToken) + { + var job = await DatabaseContext.Jobs.Where(x => x.Id == id).Include(x => x.StartedBy).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (job == default(Job)) + return NotFound(); + return Json(job.ToApi()); + } } } diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 59d2b5c19a..54d01677da 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -43,6 +43,14 @@ namespace Tgstation.Server.Host.Controllers [HttpGet] public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// + /// Attempt to get a specific a + /// + /// The for the operation + /// A resulting in the of the operation + [HttpGet("/{0}")] + public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// /// Attempt to update a /// From 75392b436216a473a8566b15996b75dbf4b0be67 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 14:58:50 -0400 Subject: [PATCH 148/397] Get it compiling but not working --- .../Components/DreamDaemon.cs | 9 +++- .../Components/DreamDaemonExecutor.cs | 4 +- .../Components/DreamMaker.cs | 2 +- .../Components/IDmbProvider.cs | 6 +-- .../Components/IDreamDaemon.cs | 6 +++ .../Components/IWatchdog.cs | 5 ++ .../Components/TemporaryDmbProvider.cs | 2 +- .../Components/Watchdog.cs | 11 ++-- .../Controllers/DreamDaemonController.cs | 53 +++++++++++++++++-- .../Controllers/JobController.cs | 1 + .../Controllers/ModelController.cs | 1 + 11 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index 1d6846ccb3..f699150079 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -27,6 +27,12 @@ namespace Tgstation.Server.Host.Components /// public bool SoftStopping { get; private set; } + /// + public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; } + + /// + public Host.Models.CompileJob LastCompileJob => watchdog.CurrentCompileJob; + /// /// The for /// @@ -69,7 +75,7 @@ namespace Tgstation.Server.Host.Components this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); - autoStart = initialSettings.AutoStart; + autoStart = initialSettings.AutoStart.Value; semaphore = new SemaphoreSlim(1); } @@ -198,6 +204,7 @@ namespace Tgstation.Server.Host.Components await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { + LastLaunchParameters = currentLaunchParameters; return currentLaunchParameters; } finally diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 0f3a47b026..38bddf9158 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -87,8 +87,8 @@ namespace Tgstation.Server.Host.Components proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", dmbProvider.DmbName, isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, - launchParameters.AllowWebClient ? "-webclient " : String.Empty, - SecurityWord(launchParameters.SecurityLevel), + launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, + SecurityWord(launchParameters.SecurityLevel.Value), DreamDaemonParameters.HostVersion, Application.Version, DreamDaemonParameters.InfoJsonPath, jsonPath); diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index ffd577f98b..8387a904ad 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -98,7 +98,7 @@ namespace Tgstation.Server.Host.Components }; using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) - using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null)) + using (var control = interop.CreateRun(launchParameters.PrimaryPort.Value, null, null)) { var interopInfo = new InteropInfo { diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs index 4825fb2914..18a5432bd2 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -22,10 +22,10 @@ namespace Tgstation.Server.Host.Components /// The secondary game directory with a trailing directory separator /// string SecondaryDirectory { get; } - + /// - /// The of the .dmb + /// The of the .dmb /// - RevisionInformation RevisionInformation { get; } + CompileJob CompileJob { get; } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index ae7dee5038..e435a3af69 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -41,8 +41,14 @@ namespace Tgstation.Server.Host.Components /// string AccessToken { get; } + /// + /// The most recently used + /// DreamDaemonLaunchParameters LastLaunchParameters { get; } + /// + /// The most recently used + /// Host.Models.CompileJob LastCompileJob { get; } /// diff --git a/src/Tgstation.Server.Host/Components/IWatchdog.cs b/src/Tgstation.Server.Host/Components/IWatchdog.cs index 59882f6c89..5504baa960 100644 --- a/src/Tgstation.Server.Host/Components/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/IWatchdog.cs @@ -8,6 +8,11 @@ namespace Tgstation.Server.Host.Components /// interface IWatchdog { + /// + /// The latest used by the + /// + Host.Models.CompileJob CurrentCompileJob { get; } + /// /// Start the /// diff --git a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs index 389256d050..fc4fa15408 100644 --- a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components public string SecondaryDirectory => throw new NotSupportedException(); /// - public RevisionInformation RevisionInformation => null; + public CompileJob CompileJob => null; /// /// Construct a diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index a98ce38c27..a5130779c7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -10,6 +10,9 @@ namespace Tgstation.Server.Host.Components /// sealed class Watchdog : IWatchdog, IDisposable { + /// + public Host.Models.CompileJob CurrentCompileJob { get; private set; } + /// /// The for the /// @@ -102,8 +105,8 @@ namespace Tgstation.Server.Host.Components interopInfo.ChatCommandsJson = String.Concat(chatJsonGuid, ".commands.json"); //set up revision - interopInfo.Revision = dmb.RevisionInformation; - foreach (var I in dmb.RevisionInformation.TestMerges) + interopInfo.Revision = dmb.CompileJob.RevisionInformation; + foreach (var I in dmb.CompileJob.RevisionInformation.TestMerges) interopInfo.TestMerges.Add(new Models.TestMerge { Author = I.Author, @@ -146,7 +149,7 @@ namespace Tgstation.Server.Host.Components ApiValidateOnly = false, HostPath = Application.HostingPath, InstanceId = instanceId, - NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, + NextPort = isPrimary ? launchParameters.SecondaryPort.Value : launchParameters.PrimaryPort.Value, //this line feels hacky, change it and remove the instanceManager dep? InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, }; @@ -216,7 +219,7 @@ namespace Tgstation.Server.Host.Components var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); - using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort, initialLaunchParameters.SecondaryPort, HandleChatMessage)) + using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort.Value, initialLaunchParameters.SecondaryPort.Value, HandleChatMessage)) { var primaryPrimedTcs = new TaskCompletionSource(); control.OnServerControl += (sender, e) => diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index d8f082abb6..a4570e2ff7 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -2,7 +2,9 @@ using Microsoft.EntityFrameworkCore; using System; using System.Linq; +using System.Linq.Expressions; using System.Net; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -15,7 +17,10 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { - [Route("/" + nameof(DreamDaemon))] + /// + /// for managing + /// + [Route("/" + nameof(Api.Models.DreamDaemon))] public sealed class DreamDaemonController : ModelController { /// @@ -32,6 +37,8 @@ namespace Tgstation.Server.Host.Controllers /// /// The for the /// The for the + /// The value of + /// The value of public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); @@ -108,14 +115,50 @@ namespace Tgstation.Server.Host.Controllers await instance.DreamDaemon.Terminate(false, cancellationToken).ConfigureAwait(false); return Ok(); } - - [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown)] + + /// + [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start)] public override async Task Update([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) { var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); - var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon) - if() + var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon); + + bool CheckModified(Expression> expression, DreamDaemonRights requiredRight) + { + var memberSelectorExpression = (MemberExpression)expression.Body; + var property = (PropertyInfo)memberSelectorExpression.Member; + + var newVal = property.GetValue(model); + if (newVal == null) + return false; + if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal) + return true; + + property.SetValue(current, newVal); + return false; + }; + + if (!CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient) + || !CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart) + || !CheckModified(x => x.PrimaryPort, DreamDaemonRights.SetPorts) + || !CheckModified(x => x.SecondaryPort, DreamDaemonRights.SetPorts) + || !CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity) + || !CheckModified(x => x.SoftRestart, DreamDaemonRights.SoftRestart)) + return Forbid(); + + //interaction with soft stop is a bit different + if (model.SoftShutdown.HasValue) + { + if (current.SoftShutdown != model.SoftShutdown && ((!current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.SoftShutdown)) || (current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.Start)))) + return Forbid(); + current.SoftShutdown = model.SoftShutdown; + } + + await instanceManager.GetInstance(Instance).DreamDaemon.ChangeSettings(current, cancellationToken).ConfigureAwait(false); + await DatabaseContext.Save(default).ConfigureAwait(false); + + return Ok(); } } } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 4be80670a2..1dcab2f526 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -70,6 +70,7 @@ namespace Tgstation.Server.Host.Controllers return Ok(); } + /// [TgsAuthorize] public override async Task GetId(long id, CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 54d01677da..aec3cf3769 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -47,6 +47,7 @@ namespace Tgstation.Server.Host.Controllers /// Attempt to get a specific a /// /// The for the operation + /// The ID of the model to get /// A resulting in the of the operation [HttpGet("/{0}")] public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); From 0d539bec549a66a1f3fa0509881f31d4152f6774 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 15:14:25 -0400 Subject: [PATCH 149/397] I think, by it's nature, Watchdog code is just destined to be shit --- .../Components/DreamDaemonExecutor.cs | 4 +-- .../Components/DreamMaker.cs | 2 +- .../Components/IDreamDaemonExecutor.cs | 3 ++- .../Components/IInteropControl.cs | 2 ++ .../Components/Watchdog.cs | 27 +++++++++++++------ 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 38bddf9158..3a54783935 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -56,7 +56,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken) + public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken) { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); @@ -86,7 +86,7 @@ namespace Tgstation.Server.Host.Components proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", dmbProvider.DmbName, - isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, + isPrimary && !asDefaultOtherServer ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel.Value), DreamDaemonParameters.HostVersion, Application.Version, diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 8387a904ad..e71476b1a4 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Components ddTcs.SetResult(null); }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, cts.Token); + var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, false, cts.Token); await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index 2f7cbc150d..77980fbe04 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -19,8 +19,9 @@ namespace Tgstation.Server.Host.Components /// The for the .dmb to run /// The for the run /// If the resulting process should never be left alive + /// If the ports will be swapped for the launch /// The for the operation /// A representing the lifetime of the process and resulting in the exit code - Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken); + Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IInteropControl.cs b/src/Tgstation.Server.Host/Components/IInteropControl.cs index 2fc3f5ea6d..43b5751415 100644 --- a/src/Tgstation.Server.Host/Components/IInteropControl.cs +++ b/src/Tgstation.Server.Host/Components/IInteropControl.cs @@ -25,5 +25,7 @@ namespace Tgstation.Server.Host.Components Task ActivateOtherServer(CancellationToken cancellationToken); Task ChangePort(ushort newPort, bool forPrimary, CancellationToken cancellationToken); + + void OnNextSwap(Action action); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index a5130779c7..d4403df47c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -91,10 +91,21 @@ namespace Tgstation.Server.Host.Components /// The path to the DreamDaemon executable /// The for the operation /// A resulting in the exit code of DreamDaemon - async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, string dreamDaemonPath, CancellationToken cancellationToken) + async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, IInteropControl control, string dreamDaemonPath, bool asDefaultOtherServer, CancellationToken cancellationToken) { using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) { + void OnBecomingCurrentServer() => CurrentCompileJob = dmb.CompileJob; + + var runningNow = launchParameters.SecondaryPort == interopInfo.NextPort; + if (asDefaultOtherServer) + runningNow = !runningNow; + + if (runningNow) + OnBecomingCurrentServer(); + else + control.OnNextSwap(OnBecomingCurrentServer); + var chatJsonGuid = Guid.NewGuid(); var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; @@ -122,7 +133,7 @@ namespace Tgstation.Server.Host.Components }); using (await chat.TrackJsons(isPrimary ? dmb.PrimaryDirectory : dmb.SecondaryDirectory, interopInfo.ChatChannelsJson, isPrimary ? interopInfo.ChatCommandsJson : null, cancellationToken).ConfigureAwait(false)) - return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, cancellationToken).ConfigureAwait(false); + return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, asDefaultOtherServer, cancellationToken).ConfigureAwait(false); } } @@ -136,7 +147,7 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// A tied to the lifetime of the resulting /// A resulting in the exit code of DreamDaemon - Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string accessToken, bool isPrimary, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) + Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, IInteropControl control, bool isPrimary, bool isDefaultOther, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) { cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try @@ -145,7 +156,7 @@ namespace Tgstation.Server.Host.Components var interopInfo = new InteropInfo { - AccessToken = accessToken, + AccessToken = isPrimary ? control.PrimaryAccessToken : control.SecondaryAccessToken, ApiValidateOnly = false, HostPath = Application.HostingPath, InstanceId = instanceId, @@ -154,7 +165,7 @@ namespace Tgstation.Server.Host.Components InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, }; - return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false); + return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, control, dreamDaemonPath, isDefaultOther, ddToken), false); } catch { @@ -229,7 +240,7 @@ namespace Tgstation.Server.Host.Components }; //start the primary server - var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control.PrimaryAccessToken, true, cancellationToken, out CancellationTokenSource primaryCts); + var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control, true, false, cancellationToken, out CancellationTokenSource primaryCts); try { //wait to make sure we got this far @@ -256,7 +267,7 @@ namespace Tgstation.Server.Host.Components { if (ddSecondaryTask == null) //start the secondary server - ddSecondaryTask = StartServer(initialLaunchParameters, null, control.SecondaryAccessToken, false, cancellationToken, out secondaryCts); + ddSecondaryTask = StartServer(initialLaunchParameters, null, control, false, false, cancellationToken, out secondaryCts); var newDmbTask = dmbFactory.OnNewerDmb(); @@ -267,7 +278,7 @@ namespace Tgstation.Server.Host.Components void PrimaryRestart() { primaryCts.Dispose(); - ddPrimaryTask = StartServer(initialLaunchParameters, null, control.PrimaryAccessToken, true, cancellationToken, out primaryCts); + ddPrimaryTask = StartServer(initialLaunchParameters, null, control, true, true, cancellationToken, out primaryCts); } void SecondaryRestart() { From c12ef68218fe5b5dd5cc6d9751846d3cfaa0ae56 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 15:16:46 -0400 Subject: [PATCH 150/397] Fix XMLdoc --- src/Tgstation.Server.Host/Components/Watchdog.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index d4403df47c..0194890f02 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -88,7 +88,9 @@ namespace Tgstation.Server.Host.Components /// The for the run /// The to be completed once the server starts if any /// The the server + /// The for the operation /// The path to the DreamDaemon executable + /// If the ports should be swapped /// The for the operation /// A resulting in the exit code of DreamDaemon async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, IInteropControl control, string dreamDaemonPath, bool asDefaultOtherServer, CancellationToken cancellationToken) @@ -138,12 +140,13 @@ namespace Tgstation.Server.Host.Components } /// - /// Locks in a version and runs a server through + /// Locks in a version and runs a server through /// /// The for the run /// The to be completed once the server starts if any - /// The access token for the server + /// The for the operation /// If a primary server is being launched + /// If the ports should be swapped /// The for the operation /// A tied to the lifetime of the resulting /// A resulting in the exit code of DreamDaemon From 3d2d5e914610c9c5064790e835136dae382b3ee4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 10:48:12 -0400 Subject: [PATCH 151/397] Upgrade docker-compose --- docker-compose.dcproj | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.dcproj b/docker-compose.dcproj index 99c91b2938..bc8f6da4b3 100644 --- a/docker-compose.dcproj +++ b/docker-compose.dcproj @@ -1,7 +1,7 @@ - 2.0 + 2.1 Linux db2a5ee6-af5c-410c-a36c-ea378fe9cbfe diff --git a/docker-compose.yml b/docker-compose.yml index 77f97d69bb..0a44a41ebe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3' +version: '3.4' services: tgstation.server: From 6d38a1a672bf763ced99d80986b3132c08c26c00 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 11:16:06 -0400 Subject: [PATCH 152/397] Handle dependencies on `Application` better --- .../Components/DreamDaemonExecutor.cs | 10 +++++-- .../Components/DreamMaker.cs | 10 +++++-- .../Components/Watchdog.cs | 10 +++++-- .../Controllers/HomeController.cs | 10 +++++-- src/Tgstation.Server.Host/Core/Application.cs | 30 ++++++++++--------- .../Core/IApplication.cs | 20 +++++++++++++ 6 files changed, 68 insertions(+), 22 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/IApplication.cs diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 0f3a47b026..5f31b01539 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -43,16 +43,22 @@ namespace Tgstation.Server.Host.Components /// The for the /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly IApplication application; /// /// Construct a /// /// The value of /// The value of - public DreamDaemonExecutor(IInstanceShutdownHandler instanceShutdownMethod, IIOManager ioManager) + /// The value of + public DreamDaemonExecutor(IInstanceShutdownHandler instanceShutdownMethod, IIOManager ioManager, IApplication application) { this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); } /// @@ -89,7 +95,7 @@ namespace Tgstation.Server.Host.Components isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel), - DreamDaemonParameters.HostVersion, Application.Version, + DreamDaemonParameters.HostVersion, application.Version, DreamDaemonParameters.InfoJsonPath, jsonPath); proc.EnableRaisingEvents = true; diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index ffd577f98b..09e460eaed 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -60,6 +60,10 @@ namespace Tgstation.Server.Host.Components /// The for /// readonly ICompileJobConsumer compileJobConsumer; + /// + /// The for + /// + readonly IApplication application; /// /// Construct @@ -70,8 +74,9 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The value of /// - public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICompileJobConsumer compileJobConsumer) + public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICompileJobConsumer compileJobConsumer, IApplication application) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); @@ -79,6 +84,7 @@ namespace Tgstation.Server.Host.Components this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); } /// @@ -103,7 +109,7 @@ namespace Tgstation.Server.Host.Components var interopInfo = new InteropInfo { ApiValidateOnly = true, - HostPath = Application.HostingPath, + HostPath = application.HostingPath, AccessToken = control.PrimaryAccessToken }; var ddTcs = new TaskCompletionSource(); diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index a98ce38c27..0617a39e36 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -39,6 +39,10 @@ namespace Tgstation.Server.Host.Components /// readonly IInstanceManager instanceManager; /// + /// The for the + /// + readonly IApplication application; + /// /// The of the /// readonly long instanceId; @@ -62,8 +66,9 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The value of /// The value of - public Watchdog(IByond byond, IChat chat, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInstanceManager instanceManager, long instanceId) + public Watchdog(IByond byond, IChat chat, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInstanceManager instanceManager, IApplication application, long instanceId) { this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); @@ -72,6 +77,7 @@ namespace Tgstation.Server.Host.Components this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); this.instanceId = instanceId; } @@ -144,7 +150,7 @@ namespace Tgstation.Server.Host.Components { AccessToken = accessToken, ApiValidateOnly = false, - HostPath = Application.HostingPath, + HostPath = application.HostingPath, InstanceId = instanceId, NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, //this line feels hacky, change it and remove the instanceManager dep? diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index d839f45017..7854609a1a 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -29,6 +29,10 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// readonly ICryptographySuite cryptographySuite; + /// + /// The for the + /// + readonly IApplication application; /// /// Construct a @@ -38,11 +42,13 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite) : base(databaseContext, authenticationContextFactory) + /// The value of + public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application) : base(databaseContext, authenticationContextFactory) { this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); } /// @@ -51,7 +57,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize] [HttpGet] - public JsonResult Home() => Json(Application.Version); + public JsonResult Home() => Json(application.Version); /// /// Attempt to authenticate a using diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 095e08b516..e730c65e8f 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -21,20 +21,14 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Core { - /// - /// Configures the ASP.NET Core web application - /// - sealed class Application + /// + sealed class Application : IApplication { - /// - /// The url the server can be reached at locally - /// - public static string HostingPath { get; private set; } + /// + public string HostingPath => serverAddresses.Addresses.First(); - /// - /// The version of the - /// - public static readonly Version Version = Assembly.GetExecutingAssembly().GetName().Version; + /// + public Version Version { get; } /// /// The for the @@ -46,6 +40,11 @@ namespace Tgstation.Server.Host.Core /// readonly Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment; + /// + /// The for the + /// + IServerAddressesFeature serverAddresses; + /// /// Construct an /// @@ -55,6 +54,8 @@ namespace Tgstation.Server.Host.Core { this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + + Version = Assembly.GetExecutingAssembly().GetName().Version; } /// @@ -147,6 +148,8 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); + + services.AddSingleton(this); } /// @@ -158,8 +161,7 @@ namespace Tgstation.Server.Host.Core if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); - var addresses = applicationBuilder.ServerFeatures.Get(); - HostingPath = addresses.Addresses.First(); + serverAddresses = applicationBuilder.ServerFeatures.Get(); if (hostingEnvironment.IsDevelopment()) applicationBuilder.UseDeveloperExceptionPage(); diff --git a/src/Tgstation.Server.Host/Core/IApplication.cs b/src/Tgstation.Server.Host/Core/IApplication.cs new file mode 100644 index 0000000000..eed6769ab3 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IApplication.cs @@ -0,0 +1,20 @@ +using System; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Configures the ASP.NET Core web application + /// + public interface IApplication + { + /// + /// The version of the + /// + Version Version { get; } + + /// + /// The url the server can be reached at locally + /// + string HostingPath { get; } + } +} \ No newline at end of file From 0e870f15199b7d2838c93f36a098d2b8e2e1a9d0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 11:25:03 -0400 Subject: [PATCH 153/397] Fix some application startup issues --- src/Tgstation.Server.Api/Models/Internal/TestMerge.cs | 4 +++- src/Tgstation.Server.Host/Components/Watchdog.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 8 ++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs b/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs index fd6052390a..715620ba93 100644 --- a/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs +++ b/src/Tgstation.Server.Api/Models/Internal/TestMerge.cs @@ -35,7 +35,9 @@ namespace Tgstation.Server.Api.Models.Internal /// The URL of the pull request /// [Required] - public Uri Url { get; set; } +#pragma warning disable CA1056 // Uri properties should not be strings + public string Url { get; set; } +#pragma warning restore CA1056 // Uri properties should not be strings /// /// The author of the pull request diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index 0617a39e36..55ce56047f 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -121,7 +121,7 @@ namespace Tgstation.Server.Host.Components PullRequestCommit = I.PullRequestRevision, TimeMerged = I.MergedAt.ToUnixTimeSeconds(), Title = I.TitleAtMerge, - Url = I.Url.ToString() + Url = I.Url }); using (await chat.TrackJsons(isPrimary ? dmb.PrimaryDirectory : dmb.SecondaryDirectory, interopInfo.ChatChannelsJson, isPrimary ? interopInfo.ChatCommandsJson : null, cancellationToken).ConfigureAwait(false)) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index e730c65e8f..963b22e98a 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -141,6 +141,8 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); @@ -149,6 +151,12 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); + + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(this); } From 619132d87f169e1b1e31c55733a1c79a1850fc62 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 11:39:44 -0400 Subject: [PATCH 154/397] Adds a basic application startup test Remember no other I/O is allowed on a new deployment without a request --- .../Core/TestApplication.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs new file mode 100644 index 0000000000..9bf014af21 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core.Tests +{ + [TestClass] + public sealed class TestApplication : IServerUpdateConsumer + { + public void ApplyUpdate(string updatePath) => throw new System.NotImplementedException(); + + [TestMethod] + public async Task TestSuccessfulStartup() + { + var dbName = Path.GetTempFileName(); + try + { + using (var cts = new CancellationTokenSource()) + using (var webHost = WebHost.CreateDefaultBuilder(new string[] { "Database:DatabaseType=Sqlite", "Database:ConnectionString=Data Source=" + dbName }) //force it to use sqlite + .UseStartup() + .Build() + ) + { + await webHost.StartAsync().ConfigureAwait(false); + await webHost.StopAsync().ConfigureAwait(false); + } + } + finally + { + File.Delete(dbName); + } + } + } +} From b64ebcc737bb6c9457592a7f002b9901e270b1d6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 11:40:09 -0400 Subject: [PATCH 155/397] Removes unesscessary CancellationTokenSource --- tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 9bf014af21..8504a9af4e 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -18,7 +18,6 @@ namespace Tgstation.Server.Host.Core.Tests var dbName = Path.GetTempFileName(); try { - using (var cts = new CancellationTokenSource()) using (var webHost = WebHost.CreateDefaultBuilder(new string[] { "Database:DatabaseType=Sqlite", "Database:ConnectionString=Data Source=" + dbName }) //force it to use sqlite .UseStartup() .Build() From 4cc817055a399d194141f0acc3b8f0a4a088a5fb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 11:40:26 -0400 Subject: [PATCH 156/397] Removes uncessary using --- tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 8504a9af4e..47ac493ecc 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; -using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Core.Tests From 5cdaba7ae0999a3cd27734d538de0c1965ef30c4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 13:05:12 -0400 Subject: [PATCH 157/397] WIP shit, waiting on the other PR --- .../Controllers/DreamMakerController.cs | 12 +- .../Controllers/InstanceController.cs | 116 ++++++++++++++++++ .../Controllers/ModelController.cs | 2 +- .../Core/DefaultIOManager.cs | 4 + src/Tgstation.Server.Host/Core/IIOManager.cs | 10 +- .../Models/DatabaseContext.cs | 26 ++-- .../Models/IDatabaseContext.cs | 17 ++- 7 files changed, 166 insertions(+), 21 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/InstanceController.cs diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index f94a51537e..0672b94895 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -1,11 +1,11 @@ -using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Core; @@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.Controllers readonly IInstanceManager instanceManager; /// - /// Construct a + /// Construct a /// /// The for the /// The for the @@ -86,7 +86,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.SetDme)] - public override async Task Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) + public override async Task UpdateAsync([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) { var hostModel = new DreamMakerSettings { diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs new file mode 100644 index 0000000000..6293642bfd --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -0,0 +1,116 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// Controller for managing s + /// + [Route("/Instance")] + public sealed class InstanceController : ModelController + { + /// + /// The for the + /// + readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + /// The value of + /// The value of + public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager) : base(databaseContext, authenticationContextFactory) + { + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + } + + /// + [TgsAuthorize(InstanceManagerRights.Create)] + public override async Task Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) + { + var dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken); + if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false)) + return Conflict(new { message = "Path not empty!" }); + + var newInstance = new Models.Instance + { + ChatSettings = new ChatSettings(), + ConfigurationAllowed = model.ConfigurationAllowed, + DreamDaemonSettings = new DreamDaemonSettings(), + DreamMakerSettings = new DreamMakerSettings(), + Name = model.Name, + Online = false, + Path = model.Path, + RepositorySettings = new RepositorySettings() + }; + + DatabaseContext.Instances.Add(newInstance); + try + { + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + + try + { + //actually reserve it now + await ioManager.CreateDirectory(model.Path, default).ConfigureAwait(false); + } + catch + { + //oh shit delete the model + DatabaseContext.Instances.Remove(newInstance); + DatabaseContext.DreamMakerSettings.Remove(newInstance.DreamMakerSettings); + DatabaseContext.ChatSettings.Remove(newInstance.ChatSettings); + DatabaseContext.RepositorySettings.Remove(newInstance.RepositorySettings); + DatabaseContext.DreamDaemonSettings.Remove(newInstance.DreamDaemonSettings); + + await DatabaseContext.Save(default).ConfigureAwait(false); + + throw; + } + } + catch (Exception e) + { + return Conflict(new { message = e.Message }); + } + + model.Online = newInstance.Online; + model.Id = newInstance.Id; + + return Json(model); + } + + /// + [TgsAuthorize(InstanceManagerRights.Relocate | InstanceManagerRights.Rename | InstanceManagerRights.SetAutoUpdate | InstanceManagerRights.SetConfiguration | InstanceManagerRights.SetOnline)] + public override async Task UpdateAsync([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) + { + var originalModel = await DatabaseContext.Instances.Where(x => x.Id == model.Id).FirstAsync(cancellationToken).ConfigureAwait(false); + if (originalModel == default(Models.Instance)) + return StatusCode(HttpStatusCode.Gone); + + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 59d2b5c19a..85cfb17bc3 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -50,7 +50,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the operation /// A resulting in the of the operation [HttpPost] - public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + public virtual Task UpdateAsync([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// /// Attempt to delete a diff --git a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs index f1cc847bc4..a5c3d59eb7 100644 --- a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs @@ -144,8 +144,12 @@ namespace Tgstation.Server.Host.Core /// public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Delete(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + /// public Task FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + /// + public Task DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + /// public string GetDirectoryName(string path) => Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path))); diff --git a/src/Tgstation.Server.Host/Core/IIOManager.cs b/src/Tgstation.Server.Host/Core/IIOManager.cs index 38df374e63..69a207e692 100644 --- a/src/Tgstation.Server.Host/Core/IIOManager.cs +++ b/src/Tgstation.Server.Host/Core/IIOManager.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Core /// /// Interface for using filesystems /// - interface IIOManager + public interface IIOManager { /// /// Retrieve the full path of some given a relative path. Must be used before passing relative paths to other APIs. All other operations in this call this internally on given paths @@ -48,6 +48,14 @@ namespace Tgstation.Server.Host.Core /// A resulting in if the file at exists, otherwise Task FileExists(string path, CancellationToken cancellationToken); + /// + /// Check that the directory at exists + /// + /// The directory to check for existence + /// The for the operation + /// A resulting in if the directory at exists, otherwise + Task DirectoryExists(string path, CancellationToken cancellationToken); + /// /// Returns all the contents of a file at as a array /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index b97991c636..8e6d4617ec 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -2,7 +2,9 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; +#if !DEBUG using System.Linq; +#endif using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; @@ -31,6 +33,15 @@ namespace Tgstation.Server.Host.Models /// public DbSet DreamMakerSettings { get; set; } + /// + public DbSet ChatSettings { get; set; } + + /// + public DbSet DreamDaemonSettings { get; set; } + + /// + public DbSet RepositorySettings { get; set; } + /// /// The for s /// @@ -40,30 +51,21 @@ namespace Tgstation.Server.Host.Models /// The s in the /// public DbSet InstanceUsers { get; set; } + /// /// The s in the /// public DbSet ChatChannels { get; set; } - /// - /// The s in the - /// - public DbSet ChatSettings { get; set; } - /// - /// The in the - /// - public DbSet DreamDaemonSettings { get; set; } + /// /// The s in the /// public DbSet Jobs { get; set; } + /// /// The s in the /// public DbSet TestMerges { get; set; } - /// - /// The in the - /// - public DbSet RepositorySettings { get; set; } /// /// The connection string for the diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index d027884bc4..e08826dcc9 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -35,10 +35,25 @@ namespace Tgstation.Server.Host.Models DbSet RevisionInformations { get; } /// - /// The in the + /// The in the /// DbSet DreamMakerSettings { get; set; } + /// + /// The in the + /// + DbSet DreamDaemonSettings { get; set; } + + /// + /// The in the + /// + DbSet ChatSettings { get; set; } + + /// + /// The in the + /// + DbSet RepositorySettings { get; set; } + /// /// Get the in the /// From 48a183a93627102dbd8c793cced27a8c13c7f2e0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 28 May 2018 13:13:25 -0400 Subject: [PATCH 158/397] Add missing test results upload --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index ebfe1041dc..98c9cb64f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -41,6 +41,8 @@ test_script: - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) From 70c8f511041ac4c4eda08784cf95b4a658c66876 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 25 Jun 2018 14:54:17 -0400 Subject: [PATCH 159/397] -i and -u to install and uninstall service --- src/Tgstation.Server.Host.Service/Program.cs | 99 ++++++++++++++++++- .../ServerService.cs | 7 +- .../Tgstation.Server.Host.Service.csproj | 6 ++ 3 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 2bbb97239c..21123e7288 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -1,4 +1,13 @@ -using System.Diagnostics.CodeAnalysis; +using McMaster.Extensions.CommandLineUtils; +using System; +using System.Collections.Specialized; +using System.Configuration.Install; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Security.Principal; using System.ServiceProcess; using Tgstation.Server.Host.Watchdog; @@ -7,12 +16,94 @@ namespace Tgstation.Server.Host.Service /// /// Contains the entrypoint for the application /// - static class Program + [ExcludeFromCodeCoverage] + class Program { + /// + /// The --uninstall or -u option + /// + [Option(ShortName = "u")] + public bool Uninstall { get; } + + /// + /// The --install or -i option + /// + [Option(ShortName = "i")] + public bool Install { get; } + + /// + /// Check if the running user is a system administrator + /// + /// if the running user is a system administrator, otherwise + static bool IsAdministrator() + { + var user = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(user); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + + /// + /// Command line handler, always runs + /// + public void OnExecute() + { + if((Install || Uninstall) && !IsAdministrator()) + { + //try to restart as admin + var currentProcess = Process.GetCurrentProcess(); + var argList = Environment.GetCommandLineArgs().ToList(); + //its windows, first arg is .exe name guaranteed + var exe = argList.First(); + argList.RemoveAt(0); + var startInfo = new ProcessStartInfo + { + UseShellExecute = true, + Verb = "runas", + Arguments = String.Join(" ", argList), + FileName = Assembly.GetExecutingAssembly().Location, + WorkingDirectory = Environment.CurrentDirectory, + }; + using (Process.Start(startInfo)) + return; + } + + if (Install) + { + if (Uninstall) + //oh no, it's retarded... + return; + using (var processInstaller = new ServiceProcessInstaller()) + using (var installer = new ServiceInstaller()) + { + processInstaller.Account = ServiceAccount.NetworkService; + + installer.Context = new InstallContext("tgs-4-install.log", new string[] { String.Format(CultureInfo.InvariantCulture, "/assemblypath={0}", Assembly.GetEntryAssembly().Location) }); + installer.Description = "/tg/station 13 server v4 running as a windows service"; + installer.DisplayName = "/tg/station server 4"; + installer.DelayedAutoStart = true; + installer.StartType = ServiceStartMode.Automatic; + installer.ServicesDependedOn = new string[] { "Tcpip", "Dhcp", "Dnscache" }; + installer.ServiceName = ServerService.Name; + installer.Parent = processInstaller; + + var state = new ListDictionary(); + installer.Install(state); + } + } + else if (Uninstall) + using (var installer = new ServiceInstaller()) + { + installer.Context = new InstallContext("tgs-4-uninstall.log", null); + installer.ServiceName = ServerService.Name; + installer.Uninstall(null); + } + else + ServiceBase.Run(new ServerService(new WatchdogFactory())); + } + /// /// Entrypoint for the application /// - [ExcludeFromCodeCoverage] - static void Main() => ServiceBase.Run(new ServerService(new WatchdogFactory())); + static int Main(string[] args) => CommandLineApplication.Execute(args); } } diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs index c7aad011cf..1242ea88fc 100644 --- a/src/Tgstation.Server.Host.Service/ServerService.cs +++ b/src/Tgstation.Server.Host.Service/ServerService.cs @@ -12,6 +12,11 @@ namespace Tgstation.Server.Host.Service /// sealed class ServerService : ServiceBase { + /// + /// The canonical windows service name + /// + public const string Name = "tgstation-server-4"; + /// /// The for the /// @@ -35,7 +40,7 @@ namespace Tgstation.Server.Host.Service { if (watchdogFactory == null) throw new ArgumentNullException(nameof(watchdogFactory)); - ServiceName = "tgstation-server"; + ServiceName = Name; watchdog = watchdogFactory.CreateWatchdog(); } diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index 530e071d08..2ea140835e 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -57,6 +57,7 @@ Tgstation.Server.Host.Service.Program + @@ -87,5 +88,10 @@ Tgstation.Server.Host.Watchdog + + + 2.2.4 + + \ No newline at end of file From d4cfc1dadfe43d7c3eb625ae21939a4709400d6e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 27 Jun 2018 10:03:19 -0400 Subject: [PATCH 160/397] Remove docker-compose --- docker-compose.dcproj | 12 ------------ docker-compose.yml | 8 -------- tgstation-server.sln | 7 ------- 3 files changed, 27 deletions(-) delete mode 100644 docker-compose.dcproj delete mode 100644 docker-compose.yml diff --git a/docker-compose.dcproj b/docker-compose.dcproj deleted file mode 100644 index 99c91b2938..0000000000 --- a/docker-compose.dcproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - 2.0 - Linux - db2a5ee6-af5c-410c-a36c-ea378fe9cbfe - - - - - - \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 77f97d69bb..0000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: '3' - -services: - tgstation.server: - image: tgstationserver - build: - context: . - dockerfile: build/Dockerfile diff --git a/tgstation-server.sln b/tgstation-server.sln index 7ceda39bca..c4739f3499 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -49,8 +49,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.CommandLin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Tests", "tests\Tgstation.Server.Host.Tests\Tgstation.Server.Host.Tests.csproj", "{A3362FF6-550F-480F-859E-8EC1EB6EAB31}" EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watchdog", "src\Tgstation.Server.Host.Watchdog\Tgstation.Server.Host.Watchdog.csproj", "{5D2D682C-6BF0-439C-850B-6AB945BBEAEA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watchdog.Tests", "tests\Tgstation.Server.Host.Watchdog.Tests\Tgstation.Server.Host.Watchdog.Tests.csproj", "{7500F776-4384-4B5F-A8D8-22461CAD108B}" @@ -184,11 +182,6 @@ Global {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Docker|Any CPU.Build.0 = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3362FF6-550F-480F-859E-8EC1EB6EAB31}.Release|Any CPU.Build.0 = Release|Any CPU - {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Docker|Any CPU.Build.0 = Release|Any CPU - {DB2A5EE6-AF5C-410C-A36C-EA378FE9CBFE}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D2D682C-6BF0-439C-850B-6AB945BBEAEA}.Docker|Any CPU.ActiveCfg = Release|Any CPU From d1891f4ec8f70415e0d54a621246f5b3e9941577 Mon Sep 17 00:00:00 2001 From: Kyle Spier-Swenson Date: Wed, 27 Jun 2018 11:27:17 -0700 Subject: [PATCH 161/397] Revert "Some watchdog stuff I forgot to merge then went looking for" --- appveyor.yml | 2 - .../Internal/DreamDaemonLaunchParameters.cs | 15 +- .../Models/Internal/DreamDaemonSettings.cs | 12 +- .../Components/DreamDaemon.cs | 9 +- .../Components/DreamDaemonExecutor.cs | 8 +- .../Components/DreamMaker.cs | 4 +- .../Components/IDmbProvider.cs | 6 +- .../Components/IDreamDaemon.cs | 10 -- .../Components/IDreamDaemonExecutor.cs | 3 +- .../Components/IInteropControl.cs | 2 - .../Components/IWatchdog.cs | 5 - .../Components/TemporaryDmbProvider.cs | 2 +- .../Components/Watchdog.cs | 45 ++--- .../Controllers/DreamDaemonController.cs | 164 ------------------ .../Controllers/DreamMakerController.cs | 7 +- .../Controllers/JobController.cs | 15 +- .../Controllers/ModelController.cs | 9 - 17 files changed, 39 insertions(+), 279 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs diff --git a/appveyor.yml b/appveyor.yml index 98c9cb64f1..ebfe1041dc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -41,8 +41,6 @@ test_script: - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - - ps: $wc = New-Object 'System.Net.WebClient' - - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index 62e0f7eb87..8602b302b5 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -13,28 +12,24 @@ namespace Tgstation.Server.Api.Models.Internal /// If the BYOND web client can be used to connect to the game server /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] - [Required] - public bool? AllowWebClient { get; set; } + public bool AllowWebClient { get; set; } /// /// The level of /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] - [Required] - public DreamDaemonSecurity? SecurityLevel { get; set; } + public DreamDaemonSecurity SecurityLevel { get; set; } /// /// The first port uses. This should be the publically advertised port /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - [Required] - public ushort? PrimaryPort { get; set; } + public ushort PrimaryPort { get; set; } /// /// The second port uses /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - [Required] - public ushort? SecondaryPort { get; set; } + public ushort SecondaryPort { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index 72260ee5fc..b4fd279881 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Rights; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -12,21 +11,18 @@ namespace Tgstation.Server.Api.Models.Internal /// If starts when it's starts /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] - [Required] - public bool? AutoStart { get; set; } + public bool AutoStart { get; set; } /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)] - [Required] - public bool? SoftRestart { get; set; } + public bool SoftRestart { get; set; } /// /// If the server is undergoing a soft shutdown /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] - [Required] - public bool? SoftShutdown { get; set; } + public bool SoftShutdown { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index f699150079..1d6846ccb3 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -27,12 +27,6 @@ namespace Tgstation.Server.Host.Components /// public bool SoftStopping { get; private set; } - /// - public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; } - - /// - public Host.Models.CompileJob LastCompileJob => watchdog.CurrentCompileJob; - /// /// The for /// @@ -75,7 +69,7 @@ namespace Tgstation.Server.Host.Components this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); - autoStart = initialSettings.AutoStart.Value; + autoStart = initialSettings.AutoStart; semaphore = new SemaphoreSlim(1); } @@ -204,7 +198,6 @@ namespace Tgstation.Server.Host.Components await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - LastLaunchParameters = currentLaunchParameters; return currentLaunchParameters; } finally diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 3a54783935..0f3a47b026 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -56,7 +56,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken) + public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken) { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); @@ -86,9 +86,9 @@ namespace Tgstation.Server.Host.Components proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", dmbProvider.DmbName, - isPrimary && !asDefaultOtherServer ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, - launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, - SecurityWord(launchParameters.SecurityLevel.Value), + isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, + launchParameters.AllowWebClient ? "-webclient " : String.Empty, + SecurityWord(launchParameters.SecurityLevel), DreamDaemonParameters.HostVersion, Application.Version, DreamDaemonParameters.InfoJsonPath, jsonPath); diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index e71476b1a4..ffd577f98b 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -98,7 +98,7 @@ namespace Tgstation.Server.Host.Components }; using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) - using (var control = interop.CreateRun(launchParameters.PrimaryPort.Value, null, null)) + using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null)) { var interopInfo = new InteropInfo { @@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Components ddTcs.SetResult(null); }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, false, cts.Token); + var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, cts.Token); await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs index 18a5432bd2..4825fb2914 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -22,10 +22,10 @@ namespace Tgstation.Server.Host.Components /// The secondary game directory with a trailing directory separator /// string SecondaryDirectory { get; } - + /// - /// The of the .dmb + /// The of the .dmb /// - CompileJob CompileJob { get; } + RevisionInformation RevisionInformation { get; } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index e435a3af69..dbd32b9d2a 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -41,16 +41,6 @@ namespace Tgstation.Server.Host.Components /// string AccessToken { get; } - /// - /// The most recently used - /// - DreamDaemonLaunchParameters LastLaunchParameters { get; } - - /// - /// The most recently used - /// - Host.Models.CompileJob LastCompileJob { get; } - /// /// Launch DreamDaemon /// diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index 77980fbe04..2f7cbc150d 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -19,9 +19,8 @@ namespace Tgstation.Server.Host.Components /// The for the .dmb to run /// The for the run /// If the resulting process should never be left alive - /// If the ports will be swapped for the launch /// The for the operation /// A representing the lifetime of the process and resulting in the exit code - Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken); + Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IInteropControl.cs b/src/Tgstation.Server.Host/Components/IInteropControl.cs index 43b5751415..2fc3f5ea6d 100644 --- a/src/Tgstation.Server.Host/Components/IInteropControl.cs +++ b/src/Tgstation.Server.Host/Components/IInteropControl.cs @@ -25,7 +25,5 @@ namespace Tgstation.Server.Host.Components Task ActivateOtherServer(CancellationToken cancellationToken); Task ChangePort(ushort newPort, bool forPrimary, CancellationToken cancellationToken); - - void OnNextSwap(Action action); } } diff --git a/src/Tgstation.Server.Host/Components/IWatchdog.cs b/src/Tgstation.Server.Host/Components/IWatchdog.cs index 5504baa960..59882f6c89 100644 --- a/src/Tgstation.Server.Host/Components/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/IWatchdog.cs @@ -8,11 +8,6 @@ namespace Tgstation.Server.Host.Components /// interface IWatchdog { - /// - /// The latest used by the - /// - Host.Models.CompileJob CurrentCompileJob { get; } - /// /// Start the /// diff --git a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs index fc4fa15408..389256d050 100644 --- a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components public string SecondaryDirectory => throw new NotSupportedException(); /// - public CompileJob CompileJob => null; + public RevisionInformation RevisionInformation => null; /// /// Construct a diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index 0194890f02..a98ce38c27 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -10,9 +10,6 @@ namespace Tgstation.Server.Host.Components /// sealed class Watchdog : IWatchdog, IDisposable { - /// - public Host.Models.CompileJob CurrentCompileJob { get; private set; } - /// /// The for the /// @@ -88,26 +85,13 @@ namespace Tgstation.Server.Host.Components /// The for the run /// The to be completed once the server starts if any /// The the server - /// The for the operation /// The path to the DreamDaemon executable - /// If the ports should be swapped /// The for the operation /// A resulting in the exit code of DreamDaemon - async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, IInteropControl control, string dreamDaemonPath, bool asDefaultOtherServer, CancellationToken cancellationToken) + async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, string dreamDaemonPath, CancellationToken cancellationToken) { using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) { - void OnBecomingCurrentServer() => CurrentCompileJob = dmb.CompileJob; - - var runningNow = launchParameters.SecondaryPort == interopInfo.NextPort; - if (asDefaultOtherServer) - runningNow = !runningNow; - - if (runningNow) - OnBecomingCurrentServer(); - else - control.OnNextSwap(OnBecomingCurrentServer); - var chatJsonGuid = Guid.NewGuid(); var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; @@ -118,8 +102,8 @@ namespace Tgstation.Server.Host.Components interopInfo.ChatCommandsJson = String.Concat(chatJsonGuid, ".commands.json"); //set up revision - interopInfo.Revision = dmb.CompileJob.RevisionInformation; - foreach (var I in dmb.CompileJob.RevisionInformation.TestMerges) + interopInfo.Revision = dmb.RevisionInformation; + foreach (var I in dmb.RevisionInformation.TestMerges) interopInfo.TestMerges.Add(new Models.TestMerge { Author = I.Author, @@ -135,22 +119,21 @@ namespace Tgstation.Server.Host.Components }); using (await chat.TrackJsons(isPrimary ? dmb.PrimaryDirectory : dmb.SecondaryDirectory, interopInfo.ChatChannelsJson, isPrimary ? interopInfo.ChatCommandsJson : null, cancellationToken).ConfigureAwait(false)) - return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, asDefaultOtherServer, cancellationToken).ConfigureAwait(false); + return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, cancellationToken).ConfigureAwait(false); } } /// - /// Locks in a version and runs a server through + /// Locks in a version and runs a server through /// /// The for the run /// The to be completed once the server starts if any - /// The for the operation + /// The access token for the server /// If a primary server is being launched - /// If the ports should be swapped /// The for the operation /// A tied to the lifetime of the resulting /// A resulting in the exit code of DreamDaemon - Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, IInteropControl control, bool isPrimary, bool isDefaultOther, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) + Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string accessToken, bool isPrimary, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) { cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try @@ -159,16 +142,16 @@ namespace Tgstation.Server.Host.Components var interopInfo = new InteropInfo { - AccessToken = isPrimary ? control.PrimaryAccessToken : control.SecondaryAccessToken, + AccessToken = accessToken, ApiValidateOnly = false, HostPath = Application.HostingPath, InstanceId = instanceId, - NextPort = isPrimary ? launchParameters.SecondaryPort.Value : launchParameters.PrimaryPort.Value, + NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, //this line feels hacky, change it and remove the instanceManager dep? InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, }; - return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, control, dreamDaemonPath, isDefaultOther, ddToken), false); + return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false); } catch { @@ -233,7 +216,7 @@ namespace Tgstation.Server.Host.Components var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); - using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort.Value, initialLaunchParameters.SecondaryPort.Value, HandleChatMessage)) + using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort, initialLaunchParameters.SecondaryPort, HandleChatMessage)) { var primaryPrimedTcs = new TaskCompletionSource(); control.OnServerControl += (sender, e) => @@ -243,7 +226,7 @@ namespace Tgstation.Server.Host.Components }; //start the primary server - var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control, true, false, cancellationToken, out CancellationTokenSource primaryCts); + var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control.PrimaryAccessToken, true, cancellationToken, out CancellationTokenSource primaryCts); try { //wait to make sure we got this far @@ -270,7 +253,7 @@ namespace Tgstation.Server.Host.Components { if (ddSecondaryTask == null) //start the secondary server - ddSecondaryTask = StartServer(initialLaunchParameters, null, control, false, false, cancellationToken, out secondaryCts); + ddSecondaryTask = StartServer(initialLaunchParameters, null, control.SecondaryAccessToken, false, cancellationToken, out secondaryCts); var newDmbTask = dmbFactory.OnNewerDmb(); @@ -281,7 +264,7 @@ namespace Tgstation.Server.Host.Components void PrimaryRestart() { primaryCts.Dispose(); - ddPrimaryTask = StartServer(initialLaunchParameters, null, control, true, true, cancellationToken, out primaryCts); + ddPrimaryTask = StartServer(initialLaunchParameters, null, control.PrimaryAccessToken, true, cancellationToken, out primaryCts); } void SecondaryRestart() { diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs deleted file mode 100644 index a4570e2ff7..0000000000 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Net; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Api.Rights; -using Tgstation.Server.Host.Components; -using Tgstation.Server.Host.Core; -using Tgstation.Server.Host.Models; -using Tgstation.Server.Host.Security; - -namespace Tgstation.Server.Host.Controllers -{ - /// - /// for managing - /// - [Route("/" + nameof(Api.Models.DreamDaemon))] - public sealed class DreamDaemonController : ModelController - { - /// - /// The for the - /// - readonly IJobManager jobManager; - /// - /// The for the - /// - readonly IInstanceManager instanceManager; - - /// - /// Construct a - /// - /// The for the - /// The for the - /// The value of - /// The value of - public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) - { - this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); - this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); - } - - /// - [TgsAuthorize(DreamDaemonRights.Start)] - public override async Task Create([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) - { - var instance = instanceManager.GetInstance(Instance); - - if (instance.DreamDaemon.Running) - return StatusCode(HttpStatusCode.Gone); - - var launchParams = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => new DreamDaemonLaunchParameters - { - AllowWebClient = x.DreamDaemonSettings.AllowWebClient, - PrimaryPort = x.DreamDaemonSettings.PrimaryPort, - SecondaryPort = x.DreamDaemonSettings.SecondaryPort, - SecurityLevel = x.DreamDaemonSettings.SecurityLevel - }).FirstAsync(cancellationToken).ConfigureAwait(false); - - await jobManager.RegisterOperation(new Models.Job - { - Description = "Launch DreamDaemon", - CancelRight = (int)DreamDaemonRights.Shutdown, - CancelRightsType = RightsType.DreamDaemon, - Instance = Instance, - StartedBy = AuthenticationContext.User - }, (job, serviceProvider, innerCt) => instance.DreamDaemon.Launch(launchParams, innerCt), cancellationToken).ConfigureAwait(false); - return Ok(); - } - - /// - [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)] - public override async Task Read(CancellationToken cancellationToken) - { - var dd = instanceManager.GetInstance(Instance).DreamDaemon; - - var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadMetadata) != 0; - var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadRevision) != 0; - - var settings = metadata ? await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false) : null; - Api.Models.DreamDaemon result = new Api.Models.DreamDaemon(); - if(metadata) - { - result.AutoStart = settings.AutoStart; - result.CurrentPort = dd.CurrentPort; - result.CurrentSecurity = dd.CurrentSecurity; - result.PrimaryPort = dd.LastLaunchParameters.PrimaryPort; - result.AllowWebClient = dd.LastLaunchParameters.AllowWebClient; - result.Running = dd.Running; - result.SecondaryPort = dd.LastLaunchParameters.SecondaryPort; - result.SecurityLevel = dd.LastLaunchParameters.SecurityLevel; - result.SoftRestart = dd.SoftRebooting; - result.SoftShutdown = dd.SoftStopping; - }; - if (revision) - result.CompileJob = dd.LastCompileJob.ToApi(); - - return Json(result); - } - - /// - [TgsAuthorize(DreamDaemonRights.Shutdown)] - public override async Task Delete([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) - { - var instance = instanceManager.GetInstance(Instance); - - if (!instance.DreamDaemon.Running) - return StatusCode(HttpStatusCode.Gone); - - await instance.DreamDaemon.Terminate(false, cancellationToken).ConfigureAwait(false); - return Ok(); - } - - /// - [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start)] - public override async Task Update([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) - { - var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); - - var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon); - - bool CheckModified(Expression> expression, DreamDaemonRights requiredRight) - { - var memberSelectorExpression = (MemberExpression)expression.Body; - var property = (PropertyInfo)memberSelectorExpression.Member; - - var newVal = property.GetValue(model); - if (newVal == null) - return false; - if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal) - return true; - - property.SetValue(current, newVal); - return false; - }; - - if (!CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient) - || !CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart) - || !CheckModified(x => x.PrimaryPort, DreamDaemonRights.SetPorts) - || !CheckModified(x => x.SecondaryPort, DreamDaemonRights.SetPorts) - || !CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity) - || !CheckModified(x => x.SoftRestart, DreamDaemonRights.SoftRestart)) - return Forbid(); - - //interaction with soft stop is a bit different - if (model.SoftShutdown.HasValue) - { - if (current.SoftShutdown != model.SoftShutdown && ((!current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.SoftShutdown)) || (current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.Start)))) - return Forbid(); - current.SoftShutdown = model.SoftShutdown; - } - - await instanceManager.GetInstance(Instance).DreamDaemon.ChangeSettings(current, cancellationToken).ConfigureAwait(false); - await DatabaseContext.Save(default).ConfigureAwait(false); - - return Ok(); - } - } -} diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index c13cb31e11..f94a51537e 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Controller for managing the compiler /// - [Route("/" + nameof(DreamMaker))] + [Route("/DreamMaker")] public sealed class DreamMakerController : ModelController { /// @@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.Controllers readonly IInstanceManager instanceManager; /// - /// Construct a + /// Construct a /// /// The for the /// The for the @@ -66,8 +66,7 @@ namespace Tgstation.Server.Host.Controllers Description = "Compile active repository code", StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.DreamMaker, - CancelRight = (int)DreamMakerRights.CancelCompile, - Instance = Instance + CancelRight = (int)DreamMakerRights.CancelCompile }; await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); return Json(job); diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 1dcab2f526..982fc72cd8 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Controllers /// /// for s /// - [Route("/" + nameof(Job))] + [Route("/Job")] public sealed class JobController : ModelController { /// @@ -60,24 +60,11 @@ namespace Tgstation.Server.Host.Controllers if (job == default(Job)) return NotFound(); - if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0) - return Forbid(); - if(job.StoppedAt != null) return StatusCode(HttpStatusCode.Gone); await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } - - /// - [TgsAuthorize] - public override async Task GetId(long id, CancellationToken cancellationToken) - { - var job = await DatabaseContext.Jobs.Where(x => x.Id == id).Include(x => x.StartedBy).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); - if (job == default(Job)) - return NotFound(); - return Json(job.ToApi()); - } } } diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index aec3cf3769..59d2b5c19a 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -43,15 +43,6 @@ namespace Tgstation.Server.Host.Controllers [HttpGet] public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); - /// - /// Attempt to get a specific a - /// - /// The for the operation - /// The ID of the model to get - /// A resulting in the of the operation - [HttpGet("/{0}")] - public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); - /// /// Attempt to update a /// From 64b4202b769933a15b84db36159edda6938c040a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 27 Jun 2018 14:36:39 -0400 Subject: [PATCH 162/397] Revert "Revert "Some watchdog stuff I forgot to merge then went looking for"" --- appveyor.yml | 2 + .../Internal/DreamDaemonLaunchParameters.cs | 15 +- .../Models/Internal/DreamDaemonSettings.cs | 12 +- .../Components/DreamDaemon.cs | 9 +- .../Components/DreamDaemonExecutor.cs | 8 +- .../Components/DreamMaker.cs | 4 +- .../Components/IDmbProvider.cs | 6 +- .../Components/IDreamDaemon.cs | 10 ++ .../Components/IDreamDaemonExecutor.cs | 3 +- .../Components/IInteropControl.cs | 2 + .../Components/IWatchdog.cs | 5 + .../Components/TemporaryDmbProvider.cs | 2 +- .../Components/Watchdog.cs | 45 +++-- .../Controllers/DreamDaemonController.cs | 164 ++++++++++++++++++ .../Controllers/DreamMakerController.cs | 7 +- .../Controllers/JobController.cs | 15 +- .../Controllers/ModelController.cs | 9 + 17 files changed, 279 insertions(+), 39 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs diff --git a/appveyor.yml b/appveyor.yml index ebfe1041dc..98c9cb64f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -41,6 +41,8 @@ test_script: - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 + - ps: $wc = New-Object 'System.Net.WebClient' + - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Console.Tests\bin\%CONFIGURATION%\netcoreapp2.0\Tgstation.Server.Host.Console.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64 - ps: $wc = New-Object 'System.Net.WebClient' - ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx)) diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index 8602b302b5..62e0f7eb87 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -12,24 +13,28 @@ namespace Tgstation.Server.Api.Models.Internal /// If the BYOND web client can be used to connect to the game server /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)] - public bool AllowWebClient { get; set; } + [Required] + public bool? AllowWebClient { get; set; } /// /// The level of /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)] - public DreamDaemonSecurity SecurityLevel { get; set; } + [Required] + public DreamDaemonSecurity? SecurityLevel { get; set; } /// /// The first port uses. This should be the publically advertised port /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort PrimaryPort { get; set; } + [Required] + public ushort? PrimaryPort { get; set; } /// /// The second port uses /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] - public ushort SecondaryPort { get; set; } + [Required] + public ushort? SecondaryPort { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs index b4fd279881..72260ee5fc 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonSettings.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal { @@ -11,18 +12,21 @@ namespace Tgstation.Server.Api.Models.Internal /// If starts when it's starts /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)] - public bool AutoStart { get; set; } + [Required] + public bool? AutoStart { get; set; } /// /// If the server is undergoing a soft reset. This may be automatically set by changes to other fields /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)] - public bool SoftRestart { get; set; } + [Required] + public bool? SoftRestart { get; set; } /// /// If the server is undergoing a soft shutdown /// [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)] - public bool SoftShutdown { get; set; } + [Required] + public bool? SoftShutdown { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs index 1d6846ccb3..f699150079 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs @@ -27,6 +27,12 @@ namespace Tgstation.Server.Host.Components /// public bool SoftStopping { get; private set; } + /// + public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; } + + /// + public Host.Models.CompileJob LastCompileJob => watchdog.CurrentCompileJob; + /// /// The for /// @@ -69,7 +75,7 @@ namespace Tgstation.Server.Host.Components this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); - autoStart = initialSettings.AutoStart; + autoStart = initialSettings.AutoStart.Value; semaphore = new SemaphoreSlim(1); } @@ -198,6 +204,7 @@ namespace Tgstation.Server.Host.Components await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { + LastLaunchParameters = currentLaunchParameters; return currentLaunchParameters; } finally diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 0f3a47b026..3a54783935 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -56,7 +56,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken) + public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken) { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); @@ -86,9 +86,9 @@ namespace Tgstation.Server.Host.Components proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", dmbProvider.DmbName, - isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, - launchParameters.AllowWebClient ? "-webclient " : String.Empty, - SecurityWord(launchParameters.SecurityLevel), + isPrimary && !asDefaultOtherServer ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, + launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, + SecurityWord(launchParameters.SecurityLevel.Value), DreamDaemonParameters.HostVersion, Application.Version, DreamDaemonParameters.InfoJsonPath, jsonPath); diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index ffd577f98b..e71476b1a4 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -98,7 +98,7 @@ namespace Tgstation.Server.Host.Components }; using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) - using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null)) + using (var control = interop.CreateRun(launchParameters.PrimaryPort.Value, null, null)) { var interopInfo = new InteropInfo { @@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Components ddTcs.SetResult(null); }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, cts.Token); + var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, false, cts.Token); await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs index 4825fb2914..18a5432bd2 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -22,10 +22,10 @@ namespace Tgstation.Server.Host.Components /// The secondary game directory with a trailing directory separator /// string SecondaryDirectory { get; } - + /// - /// The of the .dmb + /// The of the .dmb /// - RevisionInformation RevisionInformation { get; } + CompileJob CompileJob { get; } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index dbd32b9d2a..e435a3af69 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -41,6 +41,16 @@ namespace Tgstation.Server.Host.Components /// string AccessToken { get; } + /// + /// The most recently used + /// + DreamDaemonLaunchParameters LastLaunchParameters { get; } + + /// + /// The most recently used + /// + Host.Models.CompileJob LastCompileJob { get; } + /// /// Launch DreamDaemon /// diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index 2f7cbc150d..77980fbe04 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -19,8 +19,9 @@ namespace Tgstation.Server.Host.Components /// The for the .dmb to run /// The for the run /// If the resulting process should never be left alive + /// If the ports will be swapped for the launch /// The for the operation /// A representing the lifetime of the process and resulting in the exit code - Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, CancellationToken cancellationToken); + Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IInteropControl.cs b/src/Tgstation.Server.Host/Components/IInteropControl.cs index 2fc3f5ea6d..43b5751415 100644 --- a/src/Tgstation.Server.Host/Components/IInteropControl.cs +++ b/src/Tgstation.Server.Host/Components/IInteropControl.cs @@ -25,5 +25,7 @@ namespace Tgstation.Server.Host.Components Task ActivateOtherServer(CancellationToken cancellationToken); Task ChangePort(ushort newPort, bool forPrimary, CancellationToken cancellationToken); + + void OnNextSwap(Action action); } } diff --git a/src/Tgstation.Server.Host/Components/IWatchdog.cs b/src/Tgstation.Server.Host/Components/IWatchdog.cs index 59882f6c89..5504baa960 100644 --- a/src/Tgstation.Server.Host/Components/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/IWatchdog.cs @@ -8,6 +8,11 @@ namespace Tgstation.Server.Host.Components /// interface IWatchdog { + /// + /// The latest used by the + /// + Host.Models.CompileJob CurrentCompileJob { get; } + /// /// Start the /// diff --git a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs index 389256d050..fc4fa15408 100644 --- a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components public string SecondaryDirectory => throw new NotSupportedException(); /// - public RevisionInformation RevisionInformation => null; + public CompileJob CompileJob => null; /// /// Construct a diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index a98ce38c27..0194890f02 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -10,6 +10,9 @@ namespace Tgstation.Server.Host.Components /// sealed class Watchdog : IWatchdog, IDisposable { + /// + public Host.Models.CompileJob CurrentCompileJob { get; private set; } + /// /// The for the /// @@ -85,13 +88,26 @@ namespace Tgstation.Server.Host.Components /// The for the run /// The to be completed once the server starts if any /// The the server + /// The for the operation /// The path to the DreamDaemon executable + /// If the ports should be swapped /// The for the operation /// A resulting in the exit code of DreamDaemon - async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, string dreamDaemonPath, CancellationToken cancellationToken) + async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, IInteropControl control, string dreamDaemonPath, bool asDefaultOtherServer, CancellationToken cancellationToken) { using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) { + void OnBecomingCurrentServer() => CurrentCompileJob = dmb.CompileJob; + + var runningNow = launchParameters.SecondaryPort == interopInfo.NextPort; + if (asDefaultOtherServer) + runningNow = !runningNow; + + if (runningNow) + OnBecomingCurrentServer(); + else + control.OnNextSwap(OnBecomingCurrentServer); + var chatJsonGuid = Guid.NewGuid(); var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; @@ -102,8 +118,8 @@ namespace Tgstation.Server.Host.Components interopInfo.ChatCommandsJson = String.Concat(chatJsonGuid, ".commands.json"); //set up revision - interopInfo.Revision = dmb.RevisionInformation; - foreach (var I in dmb.RevisionInformation.TestMerges) + interopInfo.Revision = dmb.CompileJob.RevisionInformation; + foreach (var I in dmb.CompileJob.RevisionInformation.TestMerges) interopInfo.TestMerges.Add(new Models.TestMerge { Author = I.Author, @@ -119,21 +135,22 @@ namespace Tgstation.Server.Host.Components }); using (await chat.TrackJsons(isPrimary ? dmb.PrimaryDirectory : dmb.SecondaryDirectory, interopInfo.ChatChannelsJson, isPrimary ? interopInfo.ChatCommandsJson : null, cancellationToken).ConfigureAwait(false)) - return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, cancellationToken).ConfigureAwait(false); + return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, asDefaultOtherServer, cancellationToken).ConfigureAwait(false); } } /// - /// Locks in a version and runs a server through + /// Locks in a version and runs a server through /// /// The for the run /// The to be completed once the server starts if any - /// The access token for the server + /// The for the operation /// If a primary server is being launched + /// If the ports should be swapped /// The for the operation /// A tied to the lifetime of the resulting /// A resulting in the exit code of DreamDaemon - Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string accessToken, bool isPrimary, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) + Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, IInteropControl control, bool isPrimary, bool isDefaultOther, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) { cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try @@ -142,16 +159,16 @@ namespace Tgstation.Server.Host.Components var interopInfo = new InteropInfo { - AccessToken = accessToken, + AccessToken = isPrimary ? control.PrimaryAccessToken : control.SecondaryAccessToken, ApiValidateOnly = false, HostPath = Application.HostingPath, InstanceId = instanceId, - NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, + NextPort = isPrimary ? launchParameters.SecondaryPort.Value : launchParameters.PrimaryPort.Value, //this line feels hacky, change it and remove the instanceManager dep? InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, }; - return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false); + return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, control, dreamDaemonPath, isDefaultOther, ddToken), false); } catch { @@ -216,7 +233,7 @@ namespace Tgstation.Server.Host.Components var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); - using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort, initialLaunchParameters.SecondaryPort, HandleChatMessage)) + using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort.Value, initialLaunchParameters.SecondaryPort.Value, HandleChatMessage)) { var primaryPrimedTcs = new TaskCompletionSource(); control.OnServerControl += (sender, e) => @@ -226,7 +243,7 @@ namespace Tgstation.Server.Host.Components }; //start the primary server - var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control.PrimaryAccessToken, true, cancellationToken, out CancellationTokenSource primaryCts); + var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control, true, false, cancellationToken, out CancellationTokenSource primaryCts); try { //wait to make sure we got this far @@ -253,7 +270,7 @@ namespace Tgstation.Server.Host.Components { if (ddSecondaryTask == null) //start the secondary server - ddSecondaryTask = StartServer(initialLaunchParameters, null, control.SecondaryAccessToken, false, cancellationToken, out secondaryCts); + ddSecondaryTask = StartServer(initialLaunchParameters, null, control, false, false, cancellationToken, out secondaryCts); var newDmbTask = dmbFactory.OnNewerDmb(); @@ -264,7 +281,7 @@ namespace Tgstation.Server.Host.Components void PrimaryRestart() { primaryCts.Dispose(); - ddPrimaryTask = StartServer(initialLaunchParameters, null, control.PrimaryAccessToken, true, cancellationToken, out primaryCts); + ddPrimaryTask = StartServer(initialLaunchParameters, null, control, true, true, cancellationToken, out primaryCts); } void SecondaryRestart() { diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs new file mode 100644 index 0000000000..a4570e2ff7 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -0,0 +1,164 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// for managing + /// + [Route("/" + nameof(Api.Models.DreamDaemon))] + public sealed class DreamDaemonController : ModelController + { + /// + /// The for the + /// + readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + /// The value of + public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) + { + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + + /// + [TgsAuthorize(DreamDaemonRights.Start)] + public override async Task Create([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var instance = instanceManager.GetInstance(Instance); + + if (instance.DreamDaemon.Running) + return StatusCode(HttpStatusCode.Gone); + + var launchParams = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => new DreamDaemonLaunchParameters + { + AllowWebClient = x.DreamDaemonSettings.AllowWebClient, + PrimaryPort = x.DreamDaemonSettings.PrimaryPort, + SecondaryPort = x.DreamDaemonSettings.SecondaryPort, + SecurityLevel = x.DreamDaemonSettings.SecurityLevel + }).FirstAsync(cancellationToken).ConfigureAwait(false); + + await jobManager.RegisterOperation(new Models.Job + { + Description = "Launch DreamDaemon", + CancelRight = (int)DreamDaemonRights.Shutdown, + CancelRightsType = RightsType.DreamDaemon, + Instance = Instance, + StartedBy = AuthenticationContext.User + }, (job, serviceProvider, innerCt) => instance.DreamDaemon.Launch(launchParams, innerCt), cancellationToken).ConfigureAwait(false); + return Ok(); + } + + /// + [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)] + public override async Task Read(CancellationToken cancellationToken) + { + var dd = instanceManager.GetInstance(Instance).DreamDaemon; + + var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadMetadata) != 0; + var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadRevision) != 0; + + var settings = metadata ? await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false) : null; + Api.Models.DreamDaemon result = new Api.Models.DreamDaemon(); + if(metadata) + { + result.AutoStart = settings.AutoStart; + result.CurrentPort = dd.CurrentPort; + result.CurrentSecurity = dd.CurrentSecurity; + result.PrimaryPort = dd.LastLaunchParameters.PrimaryPort; + result.AllowWebClient = dd.LastLaunchParameters.AllowWebClient; + result.Running = dd.Running; + result.SecondaryPort = dd.LastLaunchParameters.SecondaryPort; + result.SecurityLevel = dd.LastLaunchParameters.SecurityLevel; + result.SoftRestart = dd.SoftRebooting; + result.SoftShutdown = dd.SoftStopping; + }; + if (revision) + result.CompileJob = dd.LastCompileJob.ToApi(); + + return Json(result); + } + + /// + [TgsAuthorize(DreamDaemonRights.Shutdown)] + public override async Task Delete([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var instance = instanceManager.GetInstance(Instance); + + if (!instance.DreamDaemon.Running) + return StatusCode(HttpStatusCode.Gone); + + await instance.DreamDaemon.Terminate(false, cancellationToken).ConfigureAwait(false); + return Ok(); + } + + /// + [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start)] + public override async Task Update([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + { + var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); + + var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon); + + bool CheckModified(Expression> expression, DreamDaemonRights requiredRight) + { + var memberSelectorExpression = (MemberExpression)expression.Body; + var property = (PropertyInfo)memberSelectorExpression.Member; + + var newVal = property.GetValue(model); + if (newVal == null) + return false; + if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal) + return true; + + property.SetValue(current, newVal); + return false; + }; + + if (!CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient) + || !CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart) + || !CheckModified(x => x.PrimaryPort, DreamDaemonRights.SetPorts) + || !CheckModified(x => x.SecondaryPort, DreamDaemonRights.SetPorts) + || !CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity) + || !CheckModified(x => x.SoftRestart, DreamDaemonRights.SoftRestart)) + return Forbid(); + + //interaction with soft stop is a bit different + if (model.SoftShutdown.HasValue) + { + if (current.SoftShutdown != model.SoftShutdown && ((!current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.SoftShutdown)) || (current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.Start)))) + return Forbid(); + current.SoftShutdown = model.SoftShutdown; + } + + await instanceManager.GetInstance(Instance).DreamDaemon.ChangeSettings(current, cancellationToken).ConfigureAwait(false); + await DatabaseContext.Save(default).ConfigureAwait(false); + + return Ok(); + } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index f94a51537e..c13cb31e11 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Controller for managing the compiler /// - [Route("/DreamMaker")] + [Route("/" + nameof(DreamMaker))] public sealed class DreamMakerController : ModelController { /// @@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.Controllers readonly IInstanceManager instanceManager; /// - /// Construct a + /// Construct a /// /// The for the /// The for the @@ -66,7 +66,8 @@ namespace Tgstation.Server.Host.Controllers Description = "Compile active repository code", StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.DreamMaker, - CancelRight = (int)DreamMakerRights.CancelCompile + CancelRight = (int)DreamMakerRights.CancelCompile, + Instance = Instance }; await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); return Json(job); diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 982fc72cd8..1dcab2f526 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Controllers /// /// for s /// - [Route("/Job")] + [Route("/" + nameof(Job))] public sealed class JobController : ModelController { /// @@ -60,11 +60,24 @@ namespace Tgstation.Server.Host.Controllers if (job == default(Job)) return NotFound(); + if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0) + return Forbid(); + if(job.StoppedAt != null) return StatusCode(HttpStatusCode.Gone); await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } + + /// + [TgsAuthorize] + public override async Task GetId(long id, CancellationToken cancellationToken) + { + var job = await DatabaseContext.Jobs.Where(x => x.Id == id).Include(x => x.StartedBy).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (job == default(Job)) + return NotFound(); + return Json(job.ToApi()); + } } } diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 59d2b5c19a..aec3cf3769 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -43,6 +43,15 @@ namespace Tgstation.Server.Host.Controllers [HttpGet] public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// + /// Attempt to get a specific a + /// + /// The for the operation + /// The ID of the model to get + /// A resulting in the of the operation + [HttpGet("/{0}")] + public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + /// /// Attempt to update a /// From 7ba92876ce69f847ac2e4a9b97ad893976a730eb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 28 Jun 2018 15:32:33 -0400 Subject: [PATCH 163/397] Let's rebuild from the ground up. Overhauls DreamDaemonExecutor and adds DreamDaemonSession --- src/Tgstation.Server.Host.Service/Program.cs | 2 +- .../Components/DreamDaemonExecutor.cs | 104 ++++-------------- .../Components/DreamDaemonSession.cs | 56 ++++++++++ .../Components/IDreamDaemonExecutor.cs | 25 +++-- .../Components/IDreamDaemonSession.cs | 31 ++++++ .../Controllers/ModelController.cs | 2 +- 6 files changed, 122 insertions(+), 98 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonSession.cs create mode 100644 src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 21123e7288..59c11072a7 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Service UseShellExecute = true, Verb = "runas", Arguments = String.Join(" ", argList), - FileName = Assembly.GetExecutingAssembly().Location, + FileName = exe, WorkingDirectory = Environment.CurrentDirectory, }; using (Process.Start(startInfo)) diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs index 3d53c14e87..6c08cf40ac 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs @@ -1,14 +1,8 @@ -using Newtonsoft.Json; -using System; +using System; using System.Diagnostics; using System.Globalization; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Components.Models; -using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { @@ -35,34 +29,11 @@ namespace Tgstation.Server.Host.Components } } - /// - /// The for the - /// - readonly IInstanceShutdownHandler instanceShutdownMethod; - /// - /// The for the - /// - readonly IIOManager ioManager; - /// - /// The for the - /// - readonly IApplication application; - - /// - /// Construct a - /// - /// The value of - /// The value of - /// The value of - public DreamDaemonExecutor(IInstanceShutdownHandler instanceShutdownMethod, IIOManager ioManager, IApplication application) - { - this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod)); - this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); - this.application = application ?? throw new ArgumentNullException(nameof(application)); - } + /// + public IDreamDaemonSession AttachToDreamDaemon(int processId) => new DreamDaemonSession(Process.GetProcessById(processId)); /// - public async Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken) + public IDreamDaemonSession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory) { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); @@ -70,69 +41,32 @@ namespace Tgstation.Server.Host.Components throw new ArgumentNullException(nameof(dreamDaemonPath)); if (dmbProvider == null) throw new ArgumentNullException(nameof(dmbProvider)); - if (interopInfo == null) - throw new ArgumentNullException(nameof(interopInfo)); + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); - if (interopInfo.NextPort != launchParameters.PrimaryPort && interopInfo.NextPort != launchParameters.SecondaryPort) - throw new ArgumentOutOfRangeException(nameof(interopInfo), interopInfo, "interopInfo.NextPort must match primary or secondary ports!"); + var proc = new Process(); - var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; - - //serialize the interop info to the json - var jsonPath = String.Concat(Guid.NewGuid(), ".tgs.json"); - - var json = JsonConvert.SerializeObject(interopInfo); - - - using (var proc = new Process()) + try { proc.StartInfo.FileName = dreamDaemonPath; - proc.StartInfo.WorkingDirectory = isPrimary ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory; - await ioManager.WriteAllBytes(ioManager.ConcatPath(proc.StartInfo.WorkingDirectory, jsonPath), Encoding.UTF8.GetBytes(json), cancellationToken).ConfigureAwait(false); + proc.StartInfo.WorkingDirectory = useSecondaryDirectory ? dmbProvider.SecondaryDirectory : dmbProvider.PrimaryDirectory; - proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}\"", + proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}\"", dmbProvider.DmbName, - isPrimary && !asDefaultOtherServer ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, + useSecondaryPort ? launchParameters.SecondaryPort : launchParameters.PrimaryPort, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel.Value), - DreamDaemonParameters.HostVersion, Application.Version, - DreamDaemonParameters.InfoJsonPath, jsonPath); + parameters); - proc.EnableRaisingEvents = true; - var tcs = new TaskCompletionSource(); - proc.Exited += (a, b) => tcs.SetResult(null); + proc.Start(); - try - { - proc.Start(); - - await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - - onSuccessfulStartup?.SetResult(null); - - try - { - using (cancellationToken.Register(() => tcs.SetCanceled())) - await tcs.Task.ConfigureAwait(false); - } - finally - { - if (!alwaysKill && !await instanceShutdownMethod.PreserveActiveExecutablesIfNecessary(launchParameters, interopInfo.AccessToken, proc.Id, isPrimary).ConfigureAwait(false)) - { - proc.Kill(); - proc.WaitForExit(); - } - } - - return proc.ExitCode; - } - catch (Exception e) - { - onSuccessfulStartup?.SetException(e); - throw; - } + return new DreamDaemonSession(proc); + } + catch + { + proc.Dispose(); + throw; } } - } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonSession.cs b/src/Tgstation.Server.Host/Components/DreamDaemonSession.cs new file mode 100644 index 0000000000..7420c43ce7 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonSession.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class DreamDaemonSession : IDreamDaemonSession + { + /// + public int ProcessId => process.Id; + + /// + public Task SuccessfulStartup { get; } + + /// + public Task Lifetime => lifetimeTask.Task; + + /// + /// The actual + /// + readonly Process process; + /// + /// The backing for + /// + readonly TaskCompletionSource lifetimeTask; + + /// + /// Construct a + /// + /// The value of + public DreamDaemonSession(Process process) + { + this.process = process ?? throw new ArgumentNullException(nameof(process)); + + SuccessfulStartup = Task.Factory.StartNew(() => process.WaitForInputIdle(), default, TaskCreationOptions.LongRunning, TaskScheduler.Current); + lifetimeTask = new TaskCompletionSource(); + process.EnableRaisingEvents = true; + process.Exited += (a, b) => lifetimeTask.SetResult(process.ExitCode); + } + + /// + public void Dispose() => process.Dispose(); + + /// + public void Terminate() + { + try + { + process.Kill(); + process.WaitForExit(); + } + catch (InvalidOperationException) { } + } + } +} diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index 77980fbe04..8e2e44555a 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -1,12 +1,9 @@ -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Components.Models; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components { /// - /// For running a DreamDaemon instance + /// For creating s /// interface IDreamDaemonExecutor { @@ -17,11 +14,17 @@ namespace Tgstation.Server.Host.Components /// The that is completed when dream daemon starts without crashing /// The path to the dreamdaemon executable /// The for the .dmb to run - /// The for the run - /// If the resulting process should never be left alive - /// If the ports will be swapped for the launch - /// The for the operation - /// A representing the lifetime of the process and resulting in the exit code - Task RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, string dreamDaemonPath, IDmbProvider dmbProvider, InteropInfo interopInfo, bool alwaysKill, bool asDefaultOtherServer, CancellationToken cancellationToken); + /// The value of the -params command line option + /// If the field of should be used + /// If the field of should be used + /// A new + IDreamDaemonSession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory); + + /// + /// Attach to a running instance of DreamDaemon + /// + /// The + /// A new + IDreamDaemonSession AttachToDreamDaemon(int processId); } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs new file mode 100644 index 0000000000..0adbc8b8d2 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents a dream daemon process + /// + interface IDreamDaemonSession : IDisposable + { + /// + /// The + /// + int ProcessId { get; } + + /// + /// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb + /// + Task SuccessfulStartup { get; } + + /// + /// A representing the lifetime of the and resulting in the + /// + Task Lifetime { get; } + + /// + /// Terminates the running process + /// + void Terminate(); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 0634175e06..aec3cf3769 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -59,7 +59,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the operation /// A resulting in the of the operation [HttpPost] - public virtual Task UpdateAsync([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// /// Attempt to delete a From 7ebb46ef0ced34d9e5d777693144d305f975d837 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 28 Jun 2018 15:39:09 -0400 Subject: [PATCH 164/397] Remove useless comment --- src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs index 8e2e44555a..b4cbe33837 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs @@ -16,7 +16,6 @@ namespace Tgstation.Server.Host.Components /// The for the .dmb to run /// The value of the -params command line option /// If the field of should be used - /// If the field of should be used /// A new IDreamDaemonSession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory); From 9a59ef45bb9e0e5b53669ad0669d8335edc3214a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 28 Jun 2018 17:02:18 -0400 Subject: [PATCH 165/397] More wip shit --- .../Components/DmbProvider.cs | 6 +- .../Components/DreamDaemonControl.cs | 80 +++++++++++++++++++ .../Components/DreamDaemonParameters.cs | 1 + .../DreamDaemonReattachInformation.cs | 35 ++++++++ .../Components/IDmbProvider.cs | 11 ++- .../Components/IDreamDaemonControl.cs | 47 +++++++++++ .../Components/Models/InteropInfo.cs | 5 +- 7 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonControl.cs create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs create mode 100644 src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs diff --git a/src/Tgstation.Server.Host/Components/DmbProvider.cs b/src/Tgstation.Server.Host/Components/DmbProvider.cs index 0cd3f758f1..4efe95cd07 100644 --- a/src/Tgstation.Server.Host/Components/DmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/DmbProvider.cs @@ -31,7 +31,7 @@ namespace Tgstation.Server.Host.Components /// /// The to run when is called /// - readonly Action onDispose; + Action onDispose; /// /// Construct a @@ -51,8 +51,10 @@ namespace Tgstation.Server.Host.Components /// public void Dispose() { - onDispose(); + onDispose?.Invoke(); GC.SuppressFinalize(this); } + + public void KeepAlive() => onDispose = null; } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs b/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs new file mode 100644 index 0000000000..9090a9d463 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs @@ -0,0 +1,80 @@ +using Byond.TopicSender; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + sealed class DreamDaemonControl : IDreamDaemonControl + { + public bool IsPrimary + { + get + { + CheckDisposed(); + return reattachInformation.IsPrimary; + } + } + + public IDmbProvider Dmb + { + get + { + CheckDisposed(); + return reattachInformation.Dmb; + } + } + + public ushort Port => throw new NotImplementedException(); + + readonly DreamDaemonReattachInformation reattachInformation; + + readonly IByondTopicSender byondTopicSender; + + IDreamDaemonSession session; + + public DreamDaemonControl(DreamDaemonReattachInformation reattachInformation, IDreamDaemonSession session, IByondTopicSender byondTopicSender) + { + this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); + this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); + this.session = session ?? throw new ArgumentNullException(nameof(session)); + } + + public void Dispose() + { + lock (this) + if (session != null) + { + session.Dispose(); + Dmb?.Dispose(); //will be null when released + session = null; + } + } + + void CheckDisposed() + { + if (session == null) + throw new ObjectDisposedException(nameof(DreamDaemonControl)); + } + + public DreamDaemonReattachInformation Release() + { + var tmpProvider = reattachInformation.Dmb; + reattachInformation.Dmb = null; + Dispose(); + Dmb.KeepAlive(); + reattachInformation.Dmb = tmpProvider; + return reattachInformation; + } + + public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic(new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), String.Format(CultureInfo.InvariantCulture, "?command={0}", command), cancellationToken); + + public Task SwapPorts(CancellatonToken cancellatonToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs index 4c7088c69e..c4306bca80 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs @@ -6,6 +6,7 @@ /// Host version. Do not change to keep forwards/backwards api compatibility /// public const string HostVersion = "server_service_version"; + /// /// Path to json /// diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs b/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs new file mode 100644 index 0000000000..b1702a6281 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs @@ -0,0 +1,35 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Parameters necessary for duplicating a session + /// + sealed class DreamDaemonReattachInformation + { + /// + /// Used to identify the DreamDaemon instance + /// + public Guid ExecutionId { get; set; } + + /// + /// The system process ID + /// + public int ProcessId { get; set; } + + /// + /// If the of is being used + /// + public bool IsPrimary { get; set; } + + /// + /// The port DreamDaemon was last listening on + /// + public ushort Port { get; set; } + + /// + /// The used by DreamDaemon + /// + public IDmbProvider Dmb { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs index 18a5432bd2..7ccb67316f 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -6,8 +6,8 @@ namespace Tgstation.Server.Host.Components /// /// Provides absolute paths to the latest compiled .dmbs /// - interface IDmbProvider : IDisposable - { + interface IDmbProvider : IDisposable + { /// /// The file name of the .dmb /// @@ -22,10 +22,15 @@ namespace Tgstation.Server.Host.Components /// The secondary game directory with a trailing directory separator /// string SecondaryDirectory { get; } - + /// /// The of the .dmb /// CompileJob CompileJob { get; } + + /// + /// Disposing the won't cause a cleanup of the working directory + /// + void KeepAlive(); } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs new file mode 100644 index 0000000000..76508109ea --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Handles communication with a + /// + interface IDreamDaemonControl : IDisposable + { + /// + /// If the of is being used + /// + bool IsPrimary { get; } + + /// + /// The being used + /// + IDmbProvider Dmb { get; } + + /// + /// The current port DreamDaemon is listening on + /// + ushort Port { get; } + + /// + /// Releases the without terminating it. Also calls + /// + /// which can be used to create a new similar to this one + DreamDaemonReattachInformation Release(); + + /// + /// Sends a command to DreamDaemon through /world/Topic() + /// + /// The for the operation + /// A resulting in the result of /world/Topic() + Task SendCommand(string command, CancellationToken cancellationToken); + + /// + /// Effectively swaps the values of and causing DreamDaemon to listen on the new + /// + /// The for the operation + /// A representing the running operation + Task SwapPorts(CancellatonToken cancellatonToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs b/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs index 1376421631..152d65d11a 100644 --- a/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs +++ b/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs @@ -1,10 +1,13 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Models { sealed class InteropInfo { + public Guid ExecutionId { get; set; } + public string AccessToken { get; set; } public long InstanceId { get; set; } From 85df54f4f2c4869ba462acad19608d07948c6453 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 29 Jun 2018 12:01:30 -0400 Subject: [PATCH 166/397] Methinks that's the bottom of the watchdog stack complete --- .../Components/DreamDaemonControl.cs | 159 ++++++++++++++++-- .../DreamDaemonReattachInformation.cs | 4 +- .../Components/IDreamDaemonControl.cs | 13 +- .../Components/IInstanceManager.cs | 11 +- .../Components/IInteropConsumer.cs | 20 +++ .../Components/IInteropContext.cs | 11 ++ .../Components/IInteropControl.cs | 31 ---- .../Components/IInteropRegistrar.cs | 16 ++ .../Components/InstanceManager.cs | 53 +++++- .../Components/InteropContext.cs | 29 ++++ .../Controllers/DreamMakerController.cs | 2 +- .../Controllers/InstanceController.cs | 2 +- .../Controllers/InteropController.cs | 47 ++++++ 13 files changed, 343 insertions(+), 55 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IInteropConsumer.cs create mode 100644 src/Tgstation.Server.Host/Components/IInteropContext.cs delete mode 100644 src/Tgstation.Server.Host/Components/IInteropControl.cs create mode 100644 src/Tgstation.Server.Host/Components/IInteropRegistrar.cs create mode 100644 src/Tgstation.Server.Host/Components/InteropContext.cs create mode 100644 src/Tgstation.Server.Host/Controllers/InteropController.cs diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs b/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs index 9090a9d463..85d8238029 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs @@ -1,15 +1,33 @@ using Byond.TopicSender; +using Microsoft.AspNetCore.Http; using System; -using System.Collections.Generic; using System.Globalization; -using System.Text; +using System.Net; using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components { - sealed class DreamDaemonControl : IDreamDaemonControl + /// + sealed class DreamDaemonControl : IDreamDaemonControl, IInteropConsumer { + /// + /// Generic OK response + /// + const string DMResponseOKGeneric = "OK"; + + /// + /// The server is requesting to know what port to open on + /// + const string DMQueryPortsClosed = "its_dark"; + + const string DMParameterAccessIdentifier = "access"; + const string DMParameterCommand = "command"; + const string DMParameterNewPort = "new_port"; + + const string DMCommandChangePort = "change_port"; + + /// public bool IsPrimary { get @@ -19,6 +37,7 @@ namespace Tgstation.Server.Host.Components } } + /// public IDmbProvider Dmb { get @@ -28,40 +47,121 @@ namespace Tgstation.Server.Host.Components } } - public ushort Port => throw new NotImplementedException(); + /// + public ushort Port + { + get + { + CheckDisposed(); + return reattachInformation.Port; + } + } + /// + /// The up to date + /// readonly DreamDaemonReattachInformation reattachInformation; + /// + /// The for the + /// readonly IByondTopicSender byondTopicSender; - IDreamDaemonSession session; + /// + /// The for the + /// + readonly IInteropContext interopContext; - public DreamDaemonControl(DreamDaemonReattachInformation reattachInformation, IDreamDaemonSession session, IByondTopicSender byondTopicSender) + /// + /// The for the + /// + readonly IDreamDaemonSession session; + + /// + /// The waits on when DreamDaemon currently has it's ports closed + /// + TaskCompletionSource portAssignmentTcs; + /// + /// The port to assign DreamDaemon when it queries for it + /// + ushort? nextPort; + + /// + /// If we know DreamDaemon currently has it's port closed + /// + bool portClosed; + /// + /// If the has been disposed + /// + bool disposed; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + /// The used to construct + public DreamDaemonControl(DreamDaemonReattachInformation reattachInformation, IDreamDaemonSession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar) { this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.session = session ?? throw new ArgumentNullException(nameof(session)); + if (interopRegistrar == null) + throw new ArgumentNullException(nameof(interopRegistrar)); + + interopContext = interopRegistrar.Register(reattachInformation.AccessIdentifier, this); + + portClosed = false; + disposed = false; } + /// public void Dispose() { lock (this) - if (session != null) + if (!disposed) { session.Dispose(); - Dmb?.Dispose(); //will be null when released - session = null; + interopContext.Dispose(); + Dmb?.Dispose(); //will be null when released + disposed = true; } } + /// + public Task HandleInterop(IQueryCollection query, CancellationToken cancellationToken) + { + if (query == null) + throw new ArgumentNullException(nameof(query)); + + if (query.ContainsKey(DMQueryPortsClosed)) + lock (this) + if(nextPort.HasValue) + { + var newPort = nextPort.Value; + nextPort = null; + portAssignmentTcs.SetResult(true); + portAssignmentTcs = null; + return Task.FromResult(new { new_port = newPort }); + } + return Task.FromResult(new object()); + } + + /// + /// Throws an if has been called + /// void CheckDisposed() { - if (session == null) + if (disposed) throw new ObjectDisposedException(nameof(DreamDaemonControl)); } + /// public DreamDaemonReattachInformation Release() { + CheckDisposed(); + //we still don't want to dispose the dmb yet, even though we're keeping it alive var tmpProvider = reattachInformation.Dmb; reattachInformation.Dmb = null; Dispose(); @@ -70,11 +170,44 @@ namespace Tgstation.Server.Host.Components return reattachInformation; } - public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic(new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), String.Format(CultureInfo.InvariantCulture, "?command={0}", command), cancellationToken); + /// + public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic(new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), String.Format(CultureInfo.InvariantCulture, "?{0}={1}&{2}={3}", DMParameterAccessIdentifier, reattachInformation.AccessIdentifier, DMParameterCommand, command), cancellationToken); - public Task SwapPorts(CancellatonToken cancellatonToken) + async Task SetPortImpl(ushort port, CancellationToken cancellationToken) => await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", DMCommandChangePort, DMParameterNewPort, port), cancellationToken).ConfigureAwait(false) == DMResponseOKGeneric; + + /// + public async Task ClosePort(CancellationToken cancellationToken) { - throw new NotImplementedException(); + CheckDisposed(); + if (portClosed) + return true; + if (await SetPortImpl(0, cancellationToken).ConfigureAwait(false)) + { + portClosed = true; + return true; + } + return false; + } + + /// + public async Task SetPort(ushort port, CancellationToken cancellatonToken) + { + CheckDisposed(); + if (portClosed) + { + Task toWait; + lock (this) + { + nextPort = port; + portAssignmentTcs = new TaskCompletionSource(); + toWait = portAssignmentTcs.Task; + } + await toWait.ConfigureAwait(false); + } + + if (port == 0) + throw new ArgumentOutOfRangeException(nameof(port), port, "port must not be zero!"); + return await SetPortImpl(port, cancellatonToken).ConfigureAwait(false); } } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs b/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs index b1702a6281..75b079cec8 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs @@ -8,9 +8,9 @@ namespace Tgstation.Server.Host.Components sealed class DreamDaemonReattachInformation { /// - /// Used to identify the DreamDaemon instance + /// Used to identify and authenticate the DreamDaemon instance /// - public Guid ExecutionId { get; set; } + public string AccessIdentifier { get; set; } /// /// The system process ID diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs index 76508109ea..b5ecc131b9 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs @@ -38,10 +38,17 @@ namespace Tgstation.Server.Host.Components Task SendCommand(string command, CancellationToken cancellationToken); /// - /// Effectively swaps the values of and causing DreamDaemon to listen on the new + /// Closes the world's port /// /// The for the operation - /// A representing the running operation - Task SwapPorts(CancellatonToken cancellatonToken); + /// A resulting in if the operation succeeded, otherwise + Task ClosePort(CancellationToken cancellationToken); + + /// + /// Causes the world to start listening on a + /// + /// The for the operation + /// A resulting in if the operation succeeded, otherwise + Task SetPort(ushort newPort, CancellationToken cancellatonToken); } } diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index 68a60ad247..376d070a37 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -1,4 +1,6 @@ -using System.Threading; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components @@ -39,5 +41,12 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// A representing the running operation Task MoveInstance(Host.Models.Instance metadata, string newPath, CancellationToken cancellationToken); + + /// + /// Handle a GET via world/Export + /// + /// The request query + /// A resulting in an graph that can be jsonified + Task HandleWorldExport(IQueryCollection query, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IInteropConsumer.cs b/src/Tgstation.Server.Host/Components/IInteropConsumer.cs new file mode 100644 index 0000000000..d92b1e6d92 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInteropConsumer.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Consumes interop events + /// + interface IInteropConsumer + { + /// + /// Handle an interop event + /// + /// The query from the world/Export request + /// The for the operation + /// A resulting in a jsonifiable object graph + Task HandleInterop(IQueryCollection query, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInteropContext.cs b/src/Tgstation.Server.Host/Components/IInteropContext.cs new file mode 100644 index 0000000000..09f9dc6bbc --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInteropContext.cs @@ -0,0 +1,11 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents a registration of an interop session + /// + interface IInteropContext : IDisposable + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInteropControl.cs b/src/Tgstation.Server.Host/Components/IInteropControl.cs deleted file mode 100644 index 43b5751415..0000000000 --- a/src/Tgstation.Server.Host/Components/IInteropControl.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Tgstation.Server.Host.Components -{ - /// - /// Represents a control set for one or two DreamDaemon instances - /// - interface IInteropControl : IDisposable - { - /// - /// When a server control message arrives - /// - event EventHandler OnServerControl; - - bool TwinServerMode { get; } - - bool SecondaryIsOther { get; } - - string PrimaryAccessToken { get; } - - string SecondaryAccessToken { get; } - - Task ActivateOtherServer(CancellationToken cancellationToken); - - Task ChangePort(ushort newPort, bool forPrimary, CancellationToken cancellationToken); - - void OnNextSwap(Action action); - } -} diff --git a/src/Tgstation.Server.Host/Components/IInteropRegistrar.cs b/src/Tgstation.Server.Host/Components/IInteropRegistrar.cs new file mode 100644 index 0000000000..9298a67236 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IInteropRegistrar.cs @@ -0,0 +1,16 @@ +namespace Tgstation.Server.Host.Components +{ + /// + /// Creates s for + /// + interface IInteropRegistrar + { + /// + /// Register a + /// + /// The access identifier for the entry + /// The being registered + /// A new representing the registration + IInteropContext Register(string accessIdentifier, IInteropConsumer consumer); + } +} diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 3d1a615f2c..bb4ba8fd80 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -1,6 +1,7 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Primitives; using System; using System.Collections.Generic; using System.Linq; @@ -12,8 +13,13 @@ using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { /// - sealed class InstanceManager : IInstanceManager, IHostedService + sealed class InstanceManager : IInstanceManager, IHostedService, IInteropRegistrar { + /// + /// HTTP GET query key for interop access identifiers + /// + const string AccessIdentifierQueryKey = "access"; + /// /// The for the /// @@ -30,6 +36,10 @@ namespace Tgstation.Server.Host.Components /// Map of s to respective s /// readonly Dictionary instances; + /// + /// Map of access identifiers to their respective + /// + readonly Dictionary interopConsumers; /// /// Construct an @@ -140,5 +150,42 @@ namespace Tgstation.Server.Host.Components throw new ArgumentNullException(nameof(accessToken)); throw new NotImplementedException(); } + + /// + public IInteropContext Register(string accessIdentifier, IInteropConsumer consumer) + { + if (accessIdentifier == null) + throw new ArgumentNullException(nameof(accessIdentifier)); + if (consumer == null) + throw new ArgumentNullException(nameof(consumer)); + + lock (interopConsumers) + interopConsumers.Add(accessIdentifier, consumer); + return new InteropContext(() => + { + lock (interopConsumers) + interopConsumers.Remove(accessIdentifier); + }); + } + + /// + public async Task HandleWorldExport(IQueryCollection query, CancellationToken cancellationToken) + { + if (query == null) + throw new ArgumentNullException(nameof(query)); + + if (!query.TryGetValue(AccessIdentifierQueryKey, out StringValues values)) + return null; + var accessIdentifier = values.FirstOrDefault(); + if (accessIdentifier == default) + return null; + + IInteropConsumer consumer; + lock (interopConsumers) + if (!interopConsumers.TryGetValue(accessIdentifier, out consumer)) + return null; + + return await consumer.HandleInterop(query, cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Components/InteropContext.cs b/src/Tgstation.Server.Host/Components/InteropContext.cs new file mode 100644 index 0000000000..7719419282 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/InteropContext.cs @@ -0,0 +1,29 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class InteropContext : IInteropContext + { + /// + /// The + /// + Action onDispose; + + /// + /// Construct an + /// + /// The value of + public InteropContext(Action onDispose) + { + this.onDispose = onDispose; + } + + /// + public void Dispose() + { + onDispose?.Invoke(); + onDispose = null; + } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index b8a418261a..acb861adff 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -87,7 +87,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.SetDme)] - public override async Task UpdateAsync([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) + public override async Task Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) { var hostModel = new DreamMakerSettings { diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 6293642bfd..3bb4ab83c5 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(InstanceManagerRights.Relocate | InstanceManagerRights.Rename | InstanceManagerRights.SetAutoUpdate | InstanceManagerRights.SetConfiguration | InstanceManagerRights.SetOnline)] - public override async Task UpdateAsync([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) + public override async Task Update([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) { var originalModel = await DatabaseContext.Instances.Where(x => x.Id == model.Id).FirstAsync(cancellationToken).ConfigureAwait(false); if (originalModel == default(Models.Instance)) diff --git a/src/Tgstation.Server.Host/Controllers/InteropController.cs b/src/Tgstation.Server.Host/Controllers/InteropController.cs new file mode 100644 index 0000000000..937889b482 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/InteropController.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// Handles requests from DreamDaemon + /// + [Route("/Interop")] + public sealed class InteropController : ApiController + { + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// Construct an + /// + /// The for the + /// The for the + /// The value of + public InteropController(IInstanceManager instanceManager, IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory) + { + this.instanceManager = instanceManager; + } + + /// + /// Handle a GET to the + /// + /// The for the operation + /// A resulting in the of the operation + [HttpGet] + public async Task HandleInterop(CancellationToken cancellationToken) + { + var result = await instanceManager.HandleWorldExport(Request.Query, cancellationToken).ConfigureAwait(false); + //explain things in very simple terms dream daemon can understand + if (result != null) + return Json(result); + return NotFound(); + } + } +} From ec248029babe6d484184b8d5d4501778588f5c76 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 29 Jun 2018 16:50:14 -0400 Subject: [PATCH 167/397] Moh werrk? --- .../Components/DreamDaemonControl.cs | 19 +++++++++++++++++ .../DreamDaemonReattachInformation.cs | 5 +++++ .../Components/DreamDaemonRebootState.cs | 21 +++++++++++++++++++ .../Components/IDreamDaemonControl.cs | 17 +++++++++++++-- .../Components/IDreamDaemonFactory.cs | 16 ++++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonRebootState.cs create mode 100644 src/Tgstation.Server.Host/Components/IDreamDaemonFactory.cs diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs b/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs index 85d8238029..272cf23d69 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs @@ -57,6 +57,16 @@ namespace Tgstation.Server.Host.Components } } + /// + public DreamDaemonRebootState RebootState + { + get + { + CheckDisposed(); + return reattachInformation.RebootState; + } + } + /// /// The up to date /// @@ -209,5 +219,14 @@ namespace Tgstation.Server.Host.Components throw new ArgumentOutOfRangeException(nameof(port), port, "port must not be zero!"); return await SetPortImpl(port, cancellatonToken).ConfigureAwait(false); } + + /// + public async Task SetRebootState(DreamDaemonRebootState newRebootState, CancellationToken cancellationToken) + { + var oldActive = RebootState != DreamDaemonRebootState.Normal; + var newActive = RebootState != DreamDaemonRebootState.Normal; + if (oldActive == newActive) + return true; + } } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs b/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs index 75b079cec8..5c24f46e21 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs @@ -27,6 +27,11 @@ namespace Tgstation.Server.Host.Components /// public ushort Port { get; set; } + /// + /// The current DreamDaemon reboot state + /// + public DreamDaemonRebootState RebootState { get; set; } + /// /// The used by DreamDaemon /// diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonRebootState.cs b/src/Tgstation.Server.Host/Components/DreamDaemonRebootState.cs new file mode 100644 index 0000000000..8608ab6005 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/DreamDaemonRebootState.cs @@ -0,0 +1,21 @@ +namespace Tgstation.Server.Host.Components +{ + /// + /// Represents the action to take when /world/Reboot() is called + /// + enum DreamDaemonRebootState + { + /// + /// Run DreamDaemon's normal reboot process + /// + Normal, + /// + /// Shutdown DreamDaemon + /// + Shutdown, + /// + /// Restart the DreamDaemon process + /// + Restart + } +} diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs index b5ecc131b9..81454a6047 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs @@ -7,8 +7,8 @@ namespace Tgstation.Server.Host.Components /// /// Handles communication with a /// - interface IDreamDaemonControl : IDisposable - { + interface IDreamDaemonControl : IDisposable + { /// /// If the of is being used /// @@ -24,6 +24,11 @@ namespace Tgstation.Server.Host.Components /// ushort Port { get; } + /// + /// The current + /// + DreamDaemonRebootState RebootState { get; } + /// /// Releases the without terminating it. Also calls /// @@ -50,5 +55,13 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// A resulting in if the operation succeeded, otherwise Task SetPort(ushort newPort, CancellationToken cancellatonToken); + + /// + /// Attempts to change the current to + /// + /// The new + /// The for the operation + /// A resulting in if the operation succeeded, otherwise + Task SetRebootState(DreamDaemonRebootState newRebootState, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonFactory.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonFactory.cs new file mode 100644 index 0000000000..76ae5ce3f4 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IDreamDaemonFactory.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components +{ + /// + /// Factory for s + /// + interface IDreamDaemonFactory + { + IDreamDaemonControl LaunchNew(DreamDaemonLaunchParameters launchParameters); + + Task Reattach(DreamDaemonReattachInformation reattachInformation, CancellationToken cancellationToken); + } +} From 4fb1019d30abe7f46b50ee846cc0922e0f9dafc8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Jul 2018 14:09:17 -0400 Subject: [PATCH 168/397] Fixups and renamespacing --- src/DMAPI/tgs.dm | 7 +++ .../Components/DreamDaemonParameters.cs | 15 ----- .../Components/IDreamDaemonFactory.cs | 16 ------ .../Executor.cs} | 10 ++-- .../IExecutor.cs} | 16 +++--- .../ISession.cs} | 4 +- .../ISessionManager.cs} | 22 +++---- .../Watchdog/ISessionManagerFactory.cs | 16 ++++++ .../{Models => Watchdog}/InteropInfo.cs | 2 +- .../ReattachInformation.cs} | 8 +-- .../RebootState.cs} | 10 ++-- .../Session.cs} | 8 +-- .../SessionManager.cs} | 57 ++++++++++--------- .../{Models => Watchdog}/TestMerge.cs | 2 +- 14 files changed, 95 insertions(+), 98 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs delete mode 100644 src/Tgstation.Server.Host/Components/IDreamDaemonFactory.cs rename src/Tgstation.Server.Host/Components/{DreamDaemonExecutor.cs => Watchdog/Executor.cs} (81%) rename src/Tgstation.Server.Host/Components/{IDreamDaemonExecutor.cs => Watchdog/IExecutor.cs} (60%) rename src/Tgstation.Server.Host/Components/{IDreamDaemonSession.cs => Watchdog/ISession.cs} (89%) rename src/Tgstation.Server.Host/Components/{IDreamDaemonControl.cs => Watchdog/ISessionManager.cs} (72%) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/ISessionManagerFactory.cs rename src/Tgstation.Server.Host/Components/{Models => Watchdog}/InteropInfo.cs (92%) rename src/Tgstation.Server.Host/Components/{DreamDaemonReattachInformation.cs => Watchdog/ReattachInformation.cs} (77%) rename src/Tgstation.Server.Host/Components/{DreamDaemonRebootState.cs => Watchdog/RebootState.cs} (71%) rename src/Tgstation.Server.Host/Components/{DreamDaemonSession.cs => Watchdog/Session.cs} (87%) rename src/Tgstation.Server.Host/Components/{DreamDaemonControl.cs => Watchdog/SessionManager.cs} (77%) rename src/Tgstation.Server.Host/Components/{Models => Watchdog}/TestMerge.cs (90%) diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index e68bfa9b61..eee991a8aa 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -45,6 +45,13 @@ //EVENT CODES #define TGS_EVENT_PORT_SWAP 1 //before a port change is about to happen, extra parameter is new port +#define TGS_EVENT_REBOOT_MODE_CHANGE 2 //before a reboot mode change, extras parameters are the current and new reboot mode enums + +//OTHER ENUMS + +#define TGS_REBOOT_MODE_NORMAL 0 +#define TGS_REBOOT_MODE_SHUTDOWN 1 +#define TGS_REBOOT_MODE_RESTART 2 //TODO diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs b/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs deleted file mode 100644 index c4306bca80..0000000000 --- a/src/Tgstation.Server.Host/Components/DreamDaemonParameters.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Tgstation.Server.Host.Components -{ - static class DreamDaemonParameters - { - /// - /// Host version. Do not change to keep forwards/backwards api compatibility - /// - public const string HostVersion = "server_service_version"; - - /// - /// Path to json - /// - public const string InfoJsonPath = "tgs_json"; - } -} diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonFactory.cs b/src/Tgstation.Server.Host/Components/IDreamDaemonFactory.cs deleted file mode 100644 index 76ae5ce3f4..0000000000 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; - -namespace Tgstation.Server.Host.Components -{ - /// - /// Factory for s - /// - interface IDreamDaemonFactory - { - IDreamDaemonControl LaunchNew(DreamDaemonLaunchParameters launchParameters); - - Task Reattach(DreamDaemonReattachInformation reattachInformation, CancellationToken cancellationToken); - } -} diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs similarity index 81% rename from src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs rename to src/Tgstation.Server.Host/Components/Watchdog/Executor.cs index 6c08cf40ac..718b459917 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs @@ -4,10 +4,10 @@ using System.Globalization; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class DreamDaemonExecutor : IDreamDaemonExecutor + sealed class Executor : IExecutor { /// /// Change a given into the appropriate DreamDaemon command line word @@ -30,10 +30,10 @@ namespace Tgstation.Server.Host.Components } /// - public IDreamDaemonSession AttachToDreamDaemon(int processId) => new DreamDaemonSession(Process.GetProcessById(processId)); + public ISession AttachToDreamDaemon(int processId) => new Session(Process.GetProcessById(processId)); /// - public IDreamDaemonSession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory) + public ISession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory) { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); @@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Components proc.Start(); - return new DreamDaemonSession(proc); + return new Session(proc); } catch { diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs b/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs similarity index 60% rename from src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs rename to src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs index b4cbe33837..5555d24030 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonExecutor.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs @@ -1,11 +1,11 @@ using Tgstation.Server.Api.Models.Internal; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Watchdog { /// - /// For creating s + /// For creating s /// - interface IDreamDaemonExecutor + interface IExecutor { /// /// Run a dream daemon instance @@ -16,14 +16,14 @@ namespace Tgstation.Server.Host.Components /// The for the .dmb to run /// The value of the -params command line option /// If the field of should be used - /// A new - IDreamDaemonSession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory); + /// A new + ISession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory); /// /// Attach to a running instance of DreamDaemon /// - /// The - /// A new - IDreamDaemonSession AttachToDreamDaemon(int processId); + /// The + /// A new + ISession AttachToDreamDaemon(int processId); } } diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs similarity index 89% rename from src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs rename to src/Tgstation.Server.Host/Components/Watchdog/ISession.cs index 0adbc8b8d2..5404306202 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonSession.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs @@ -1,12 +1,12 @@ using System; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Watchdog { /// /// Represents a dream daemon process /// - interface IDreamDaemonSession : IDisposable + interface ISession : IDisposable { /// /// The diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs similarity index 72% rename from src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs rename to src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs index 81454a6047..613009b02b 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemonControl.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs @@ -2,12 +2,12 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Watchdog { /// - /// Handles communication with a + /// Handles communication with a /// - interface IDreamDaemonControl : IDisposable + interface ISessionManager : IDisposable { /// /// If the of is being used @@ -22,18 +22,18 @@ namespace Tgstation.Server.Host.Components /// /// The current port DreamDaemon is listening on /// - ushort Port { get; } + ushort? Port { get; } /// - /// The current + /// The current /// - DreamDaemonRebootState RebootState { get; } + RebootState RebootState { get; } /// - /// Releases the without terminating it. Also calls + /// Releases the without terminating it. Also calls /// - /// which can be used to create a new similar to this one - DreamDaemonReattachInformation Release(); + /// which can be used to create a new similar to this one + ReattachInformation Release(); /// /// Sends a command to DreamDaemon through /world/Topic() @@ -59,9 +59,9 @@ namespace Tgstation.Server.Host.Components /// /// Attempts to change the current to /// - /// The new + /// The new /// The for the operation /// A resulting in if the operation succeeded, otherwise - Task SetRebootState(DreamDaemonRebootState newRebootState, CancellationToken cancellationToken); + Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionManagerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionManagerFactory.cs new file mode 100644 index 0000000000..0dfa13b376 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionManagerFactory.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// Factory for s + /// + interface ISessionManagerFactory + { + ISessionManager LaunchNew(DreamDaemonLaunchParameters launchParameters); + + Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs b/src/Tgstation.Server.Host/Components/Watchdog/InteropInfo.cs similarity index 92% rename from src/Tgstation.Server.Host/Components/Models/InteropInfo.cs rename to src/Tgstation.Server.Host/Components/Watchdog/InteropInfo.cs index 152d65d11a..a68aaa5bf0 100644 --- a/src/Tgstation.Server.Host/Components/Models/InteropInfo.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/InteropInfo.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Tgstation.Server.Api.Models.Internal; -namespace Tgstation.Server.Host.Components.Models +namespace Tgstation.Server.Host.Components.Watchdog { sealed class InteropInfo { diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs similarity index 77% rename from src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs rename to src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs index 5c24f46e21..cc852a69fd 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs @@ -1,11 +1,11 @@ using System; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Watchdog { /// - /// Parameters necessary for duplicating a session + /// Parameters necessary for duplicating a session /// - sealed class DreamDaemonReattachInformation + sealed class ReattachInformation { /// /// Used to identify and authenticate the DreamDaemon instance @@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.Components /// /// The current DreamDaemon reboot state /// - public DreamDaemonRebootState RebootState { get; set; } + public RebootState RebootState { get; set; } /// /// The used by DreamDaemon diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonRebootState.cs b/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs similarity index 71% rename from src/Tgstation.Server.Host/Components/DreamDaemonRebootState.cs rename to src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs index 8608ab6005..b1fc80317a 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonRebootState.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs @@ -1,21 +1,21 @@ -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Watchdog { /// /// Represents the action to take when /world/Reboot() is called /// - enum DreamDaemonRebootState + enum RebootState : int { /// /// Run DreamDaemon's normal reboot process /// - Normal, + Normal = 0, /// /// Shutdown DreamDaemon /// - Shutdown, + Shutdown = 1, /// /// Restart the DreamDaemon process /// - Restart + Restart = 2 } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonSession.cs b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs similarity index 87% rename from src/Tgstation.Server.Host/Components/DreamDaemonSession.cs rename to src/Tgstation.Server.Host/Components/Watchdog/Session.cs index 7420c43ce7..138be06b17 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonSession.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs @@ -2,10 +2,10 @@ using System.Diagnostics; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class DreamDaemonSession : IDreamDaemonSession + sealed class Session : ISession { /// public int ProcessId => process.Id; @@ -26,10 +26,10 @@ namespace Tgstation.Server.Host.Components readonly TaskCompletionSource lifetimeTask; /// - /// Construct a + /// Construct a /// /// The value of - public DreamDaemonSession(Process process) + public Session(Process process) { this.process = process ?? throw new ArgumentNullException(nameof(process)); diff --git a/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionManager.cs similarity index 77% rename from src/Tgstation.Server.Host/Components/DreamDaemonControl.cs rename to src/Tgstation.Server.Host/Components/Watchdog/SessionManager.cs index 272cf23d69..b2012b7873 100644 --- a/src/Tgstation.Server.Host/Components/DreamDaemonControl.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionManager.cs @@ -6,10 +6,10 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class DreamDaemonControl : IDreamDaemonControl, IInteropConsumer + sealed class SessionManager : ISessionManager, IInteropConsumer { /// /// Generic OK response @@ -24,8 +24,10 @@ namespace Tgstation.Server.Host.Components const string DMParameterAccessIdentifier = "access"; const string DMParameterCommand = "command"; const string DMParameterNewPort = "new_port"; + const string DMParameterNewRebootMode = "new_reboot_mode"; const string DMCommandChangePort = "change_port"; + const string DMCommandChangeReboot = "change_reboot"; /// public bool IsPrimary @@ -48,17 +50,19 @@ namespace Tgstation.Server.Host.Components } /// - public ushort Port + public ushort? Port { get { CheckDisposed(); + if (portClosed) + return null; return reattachInformation.Port; } } /// - public DreamDaemonRebootState RebootState + public RebootState RebootState { get { @@ -68,24 +72,24 @@ namespace Tgstation.Server.Host.Components } /// - /// The up to date + /// The up to date /// - readonly DreamDaemonReattachInformation reattachInformation; + readonly ReattachInformation reattachInformation; /// - /// The for the + /// The for the /// readonly IByondTopicSender byondTopicSender; /// - /// The for the + /// The for the /// readonly IInteropContext interopContext; /// - /// The for the + /// The for the /// - readonly IDreamDaemonSession session; + readonly ISession session; /// /// The waits on when DreamDaemon currently has it's ports closed @@ -101,18 +105,18 @@ namespace Tgstation.Server.Host.Components /// bool portClosed; /// - /// If the has been disposed + /// If the has been disposed /// bool disposed; /// - /// Construct a + /// Construct a /// /// The value of /// The value of /// The value of /// The used to construct - public DreamDaemonControl(DreamDaemonReattachInformation reattachInformation, IDreamDaemonSession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar) + public SessionManager(ReattachInformation reattachInformation, ISession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar) { this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); @@ -130,13 +134,14 @@ namespace Tgstation.Server.Host.Components public void Dispose() { lock (this) - if (!disposed) - { - session.Dispose(); - interopContext.Dispose(); - Dmb?.Dispose(); //will be null when released - disposed = true; - } + { + if (disposed) + return; + session.Dispose(); + interopContext.Dispose(); + Dmb?.Dispose(); //will be null when released + disposed = true; + } } /// @@ -164,11 +169,11 @@ namespace Tgstation.Server.Host.Components void CheckDisposed() { if (disposed) - throw new ObjectDisposedException(nameof(DreamDaemonControl)); + throw new ObjectDisposedException(nameof(SessionManager)); } /// - public DreamDaemonReattachInformation Release() + public ReattachInformation Release() { CheckDisposed(); //we still don't want to dispose the dmb yet, even though we're keeping it alive @@ -221,12 +226,12 @@ namespace Tgstation.Server.Host.Components } /// - public async Task SetRebootState(DreamDaemonRebootState newRebootState, CancellationToken cancellationToken) + public async Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken) { - var oldActive = RebootState != DreamDaemonRebootState.Normal; - var newActive = RebootState != DreamDaemonRebootState.Normal; - if (oldActive == newActive) + if (RebootState == newRebootState) return true; + + return await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", DMCommandChangeReboot, DMParameterNewRebootMode, (int)newRebootState), cancellationToken).ConfigureAwait(false) == DMResponseOKGeneric; } } } diff --git a/src/Tgstation.Server.Host/Components/Models/TestMerge.cs b/src/Tgstation.Server.Host/Components/Watchdog/TestMerge.cs similarity index 90% rename from src/Tgstation.Server.Host/Components/Models/TestMerge.cs rename to src/Tgstation.Server.Host/Components/Watchdog/TestMerge.cs index cda4aaa5ea..7a55e739fe 100644 --- a/src/Tgstation.Server.Host/Components/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/TestMerge.cs @@ -1,6 +1,6 @@ using Tgstation.Server.Api.Models.Internal; -namespace Tgstation.Server.Host.Components.Models +namespace Tgstation.Server.Host.Components.Watchdog { /// /// This model mirrors /datum/tgs_revision_information/test_merge From 69cb412d9df910e7bae20a5cae045a0c93901d08 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Jul 2018 14:39:01 -0400 Subject: [PATCH 169/397] WatchdogFactory done +other shit --- .../Components/IDmbFactory.cs | 1 - .../Components/IDreamMaker.cs | 3 +- .../Components/IInstanceManager.cs | 1 - .../Components/InstanceManager.cs | 8 +-- .../Components/Watchdog.cs | 1 - .../Components/Watchdog/IWatchdog.cs | 7 +++ .../Components/Watchdog/IWatchdogFactory.cs | 17 ++++++ .../Components/Watchdog/Watchdog.cs | 19 +++++++ .../Components/Watchdog/WatchdogFactory.cs | 53 +++++++++++++++++++ 9 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/IDmbFactory.cs index 19ca0dc1ae..2ed1a771ff 100644 --- a/src/Tgstation.Server.Host/Components/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/IDmbFactory.cs @@ -1,6 +1,5 @@ using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 938aeb46b8..cbbfb1deb9 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Hosting; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Models; diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index 376d070a37..1eb1454dd2 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Http; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index bb4ba8fd80..ef150c8552 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -56,7 +56,7 @@ namespace Tgstation.Server.Host.Components } /// - public IInstance GetInstance(Host.Models.Instance metadata) + public IInstance GetInstance(Models.Instance metadata) { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); @@ -69,7 +69,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task MoveInstance(Host.Models.Instance instance, string newPath, CancellationToken cancellationToken) + public async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken) { if (newPath == null) throw new ArgumentNullException(nameof(newPath)); @@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OfflineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) + public async Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken) { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); @@ -110,7 +110,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) + public async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken) { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs index ff86d9d0ab..474beeeaf5 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Components.Models; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs new file mode 100644 index 0000000000..e5707760ac --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -0,0 +1,7 @@ +namespace Tgstation.Server.Host.Components.Watchdog +{ + interface IWatchdog + { + + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs new file mode 100644 index 0000000000..4e9c98235a --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs @@ -0,0 +1,17 @@ +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// For creating s + /// + interface IWatchdogFactory + { + /// + /// Creates a + /// + /// The for the with + /// A new + IWatchdog CreateWatchdog(IDmbFactory dmbFactory); + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs new file mode 100644 index 0000000000..4630419674 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -0,0 +1,19 @@ +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + sealed class Watchdog : IWatchdog + { + /// + /// Construct a + /// + /// + /// + /// + /// + /// + /// + public Watchdog(IByond byond, IChat chat, ISessionManagerFactory sessionManagerFactory, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar) + { + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs new file mode 100644 index 0000000000..9677ef6d12 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -0,0 +1,53 @@ +using System; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + sealed class WatchdogFactory : IWatchdogFactory + { + /// + /// The for the + /// + readonly IByond byond; + + /// + /// The for the + /// + readonly IChat chat; + + /// + /// The for the + /// + readonly ISessionManagerFactory sessionManagerFactory; + + /// + /// The for the + /// + readonly IEventConsumer eventConsumer; + + /// + /// The for the + /// + readonly IInteropRegistrar interopRegistrar; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + public WatchdogFactory(IByond byond, IChat chat, ISessionManagerFactory sessionManagerFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar) + { + this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); + this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); + this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.interopRegistrar = interopRegistrar ?? throw new ArgumentNullException(nameof(interopRegistrar)); + } + + /// + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory) => new Watchdog(byond, chat, sessionManagerFactory, dmbFactory, eventConsumer, interopRegistrar); + } +} From ae74502c7e9e202ad9304ce00aa7fed260006b52 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Jul 2018 15:31:26 -0400 Subject: [PATCH 170/397] Keep on keeping on --- src/Tgstation.Server.Api/Models/Byond.cs | 8 +- src/Tgstation.Server.Api/Models/CompileJob.cs | 9 ++- .../Rights/ByondRights.cs | 4 +- .../Components/Watchdog/ISession.cs | 17 +---- .../Components/Watchdog/ISessionBase.cs | 18 +++++ .../Components/Watchdog/ISessionManager.cs | 5 +- .../Components/Watchdog/IWatchdog.cs | 15 +++- .../Components/Watchdog/IWatchdogFactory.cs | 5 +- .../Components/Watchdog/LaunchResult.cs | 25 +++++++ .../Components/Watchdog/Session.cs | 21 +++++- .../Components/Watchdog/Watchdog.cs | 74 +++++++++++++++++-- .../Components/Watchdog/WatchdogFactory.cs | 3 +- .../Watchdog/WatchdogLaunchResult.cs | 31 ++++++++ 13 files changed, 193 insertions(+), 42 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/ISessionBase.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index 7327b27cbd..6654babe0a 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -16,15 +16,15 @@ namespace Tgstation.Server.Api.Models public ByondStatus ByondStatus { get; set; } /// - /// The of the installation. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers + /// The of the installation used for new compiles. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers /// [Permissions(ReadRight = ByondRights.ReadInstalled, WriteRight = ByondRights.ChangeVersion)] public Version Version { get; set; } /// - /// The of the that's staged for installation + /// The of the that's currently running the game servers /// - [Permissions(DenyWrite = true, ReadRight = ByondRights.ReadStaged)] - public Version StagedVersion { get; set; } + [Permissions(DenyWrite = true, ReadRight = ByondRights.ReadPrevious)] + public Version PreviousVersion { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 211ff95951..44a3b7baad 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Api.Models +using System; + +namespace Tgstation.Server.Api.Models { /// public sealed class CompileJob : Internal.CompileJob @@ -12,5 +14,10 @@ /// Git revision the compiler ran on. Not modifiable /// public RevisionInformation RevisionInformation { get; set; } + + /// + /// The the was made with + /// + public Version ByondVersion { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs index 4919ef2800..142ea098b4 100644 --- a/src/Tgstation.Server.Api/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -17,9 +17,9 @@ namespace Tgstation.Server.Api.Rights /// ReadInstalled = 1, /// - /// User may check the staged BYOND version + /// User may check the previous BYOND version /// - ReadStaged = 2, + ReadPrevious = 2, /// /// User may change to any BYOND version /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs index 5404306202..5fb7fb35dd 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISession.cs @@ -1,28 +1,15 @@ -using System; -using System.Threading.Tasks; - -namespace Tgstation.Server.Host.Components.Watchdog +namespace Tgstation.Server.Host.Components.Watchdog { /// /// Represents a dream daemon process /// - interface ISession : IDisposable + interface ISession : ISessionBase { /// /// The /// int ProcessId { get; } - /// - /// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb - /// - Task SuccessfulStartup { get; } - - /// - /// A representing the lifetime of the and resulting in the - /// - Task Lifetime { get; } - /// /// Terminates the running process /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionBase.cs new file mode 100644 index 0000000000..672fbee8cf --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionBase.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + interface ISessionBase : IDisposable + { + /// + /// A that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb or when it crashes + /// + Task LaunchResult { get; } + + /// + /// A representing the lifetime of the and resulting in the + /// + Task Lifetime { get; } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs index 613009b02b..e793e3cbcc 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Watchdog @@ -7,7 +6,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Handles communication with a /// - interface ISessionManager : IDisposable + interface ISessionManager : ISessionBase { /// /// If the of is being used diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index e5707760ac..ed8ca4899b 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -1,7 +1,14 @@ -namespace Tgstation.Server.Host.Components.Watchdog -{ - interface IWatchdog - { +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; +namespace Tgstation.Server.Host.Components.Watchdog +{ + interface IWatchdog : IDisposable + { + DreamDaemonLaunchParameters LaunchParameters { get; set; } + + Task Launch(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs index 4e9c98235a..75407159ec 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs @@ -10,8 +10,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Creates a /// - /// The for the with + /// The for the with + /// The initial for the /// A new - IWatchdog CreateWatchdog(IDmbFactory dmbFactory); + IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs new file mode 100644 index 0000000000..8f8ae755ab --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs @@ -0,0 +1,25 @@ +using System; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// Represents the result of trying to start a DD process + /// + sealed class LaunchResult + { + /// + /// The time it took for to return + /// + public TimeSpan StartupTime { get; set; } + + /// + /// The if it exited + /// + public int? ExitCode { get; set; } + + /// + /// The peak virtual memory usage in bytes + /// + public long PeakMemory { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs index 138be06b17..61030ee4d4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public int ProcessId => process.Id; /// - public Task SuccessfulStartup { get; } + public Task Startup { get; } /// public Task Lifetime => lifetimeTask.Task; @@ -33,7 +33,24 @@ namespace Tgstation.Server.Host.Components.Watchdog { this.process = process ?? throw new ArgumentNullException(nameof(process)); - SuccessfulStartup = Task.Factory.StartNew(() => process.WaitForInputIdle(), default, TaskCreationOptions.LongRunning, TaskScheduler.Current); + Startup = Task.Factory.StartNew(() => + { + var startTime = DateTimeOffset.Now; + try + { + process.WaitForInputIdle(); + } + catch (InvalidOperationException) { } + var result = new LaunchResult + { + ExitCode = process.HasExited ? (int?)process.ExitCode : null, + PeakMemory = process.PeakWorkingSet64, + StartupTime = DateTimeOffset.Now - startTime + }; + if (result.PeakMemory == 0) //linux, best we can do honestly, test if this even works + result.PeakMemory = process.WorkingSet64; + return result; + }, default, TaskCreationOptions.LongRunning, TaskScheduler.Current); lifetimeTask = new TaskCompletionSource(); process.EnableRaisingEvents = true; process.Exited += (a, b) => lifetimeTask.SetResult(process.ExitCode); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 4630419674..4bfb26a678 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -1,19 +1,77 @@ -namespace Tgstation.Server.Host.Components.Watchdog +using System; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components.Watchdog { /// sealed class Watchdog : IWatchdog { + public DreamDaemonLaunchParameters LaunchParameters + { + get => launchParameters; + set => launchParameters = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// The for the + /// + readonly IByond byond; + + /// + /// The for the + /// + readonly IChat chat; + + /// + /// The for the + /// + readonly ISessionManagerFactory sessionManagerFactory; + + /// + /// The for the + /// + readonly IEventConsumer eventConsumer; + + /// + /// The for the + /// + readonly IInteropRegistrar interopRegistrar; + + /// + /// The for the + /// + readonly IDmbFactory dmbFactory; + + DreamDaemonLaunchParameters launchParameters; + + ISessionManager alphaServer; + ISessionManager bravoServer; + /// /// Construct a /// - /// - /// - /// - /// - /// - /// - public Watchdog(IByond byond, IChat chat, ISessionManagerFactory sessionManagerFactory, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar) + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + public Watchdog(IByond byond, IChat chat, ISessionManagerFactory sessionManagerFactory, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar, DreamDaemonLaunchParameters initialLaunchParameters) { + this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); + this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); + this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); + this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.interopRegistrar = interopRegistrar ?? throw new ArgumentNullException(nameof(interopRegistrar)); + LaunchParameters = initialLaunchParameters; + } + + /// + public void Dispose() + { + alphaServer?.Dispose(); + bravoServer?.Dispose(); } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 9677ef6d12..f7030c2dda 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -1,4 +1,5 @@ using System; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Watchdog { @@ -48,6 +49,6 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory) => new Watchdog(byond, chat, sessionManagerFactory, dmbFactory, eventConsumer, interopRegistrar); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParamaters) => new Watchdog(byond, chat, sessionManagerFactory, dmbFactory, eventConsumer, interopRegistrar, launchParameters); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs new file mode 100644 index 0000000000..3eb27b555a --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs @@ -0,0 +1,31 @@ +using System; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// Launch results for a + /// + sealed class WatchdogLaunchResult + { + /// + /// If the watchdog is running + /// + bool Running { + get { + if (Alpha == null || Bravo == null) + throw new InvalidOperationException("Alpha or Bravo is null!"); + return !Alpha.ExitCode.HasValue && !Bravo.ExitCode.HasValue; + } + } + + /// + /// The for the alpha process + /// + LaunchResult Alpha { get; set; } + + /// + /// The for the bravo process + /// + LaunchResult Bravo { get; set; } + } +} From 72a7adc40c46bce3ce082545a36135f5adf5e283 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Jul 2018 16:46:16 -0400 Subject: [PATCH 171/397] Always dreaming never stopping --- src/DMAPI/tgs/v4/api.dm | 18 +- src/DMAPI/tgs/v4/commands.dm | 2 +- src/Tgstation.Server.Api/Models/CompileJob.cs | 5 - .../Models/Internal/CompileJob.cs | 5 + .../Components/IByond.cs | 9 +- .../Components/IByondExecutableLock.cs | 12 + src/Tgstation.Server.Host/Components/IChat.cs | 4 +- .../Components/IChatJsonTrackingContext.cs | 8 + .../Components/Watchdog.cs | 402 ------------------ ...essionManager.cs => ISessionController.cs} | 4 +- .../Watchdog/ISessionControllerFactory.cs | 16 + .../Watchdog/ISessionManagerFactory.cs | 16 - .../Components/Watchdog/IWatchdog.cs | 2 +- .../Components/Watchdog/InteropInfo.cs | 12 +- .../Watchdog/ReattachInformation.cs | 12 +- .../Components/Watchdog/Session.cs | 4 +- ...SessionManager.cs => SessionController.cs} | 37 +- .../Watchdog/SessionControllerFactory.cs | 150 +++++++ .../Components/Watchdog/Watchdog.cs | 29 +- .../Components/Watchdog/WatchdogFactory.cs | 8 +- .../Watchdog/WatchdogReattachInformation.cs | 23 + 21 files changed, 295 insertions(+), 483 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/IByondExecutableLock.cs create mode 100644 src/Tgstation.Server.Host/Components/IChatJsonTrackingContext.cs delete mode 100644 src/Tgstation.Server.Host/Components/Watchdog.cs rename src/Tgstation.Server.Host/Components/Watchdog/{ISessionManager.cs => ISessionController.cs} (95%) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs delete mode 100644 src/Tgstation.Server.Host/Components/Watchdog/ISessionManagerFactory.cs rename src/Tgstation.Server.Host/Components/Watchdog/{SessionManager.cs => SessionController.cs} (82%) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 5b836939cf..b5991119d4 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -16,14 +16,11 @@ #define TGS4_COMM_CHAT "tgs_chat_send" /datum/tgs_api/v4 - var/access_token - var/instance_id + var/access_identifier var/instance_name var/host_path - var/port_1 - var/port_2 - var/cached_json - var/json_path + var/chat_channels_json_path + var/chat_commands_json_path var/list/intercepted_message_queue @@ -46,7 +43,7 @@ if(!json_file) TGS_ERROR_LOG("Missing specified json file: [json_path]") return - cached_json = json_decode(json_file) + var/cached_json = json_decode(json_file) if(!cached_json) TGS_ERROR_LOG("Failed to decode info json: [json_file]") return @@ -57,7 +54,9 @@ if(cached_json["api_validate_only"]) Export(TGS4_COMM_VALIDATE) del(world) - + + chat_channels_json_path = cached_json["chat_channels_json"] + chat_commands_json_path = cached_json["chat_commands_json"] src.event_handler = event_handler instance_name = cached_json["instance_name"] port_1 = world.port @@ -197,8 +196,7 @@ /datum/tgs_api/v4/ChatChannelInfo() . = list() //no caching cause tgs may change this - var/chat_json_path = cached_json["chat_channels_json"] - var/list/json = json_decode(file2text(chat_json_path)) + var/list/json = json_decode(file2text(chat_channels_json_path)) for(var/I in json) var/datum/tgs_chat_channel/channel = new channel.id = I["id"] diff --git a/src/DMAPI/tgs/v4/commands.dm b/src/DMAPI/tgs/v4/commands.dm index 0d4e72f0fc..962a497db6 100644 --- a/src/DMAPI/tgs/v4/commands.dm +++ b/src/DMAPI/tgs/v4/commands.dm @@ -15,7 +15,7 @@ results[command_name] = list("help_text" = stc.help_text, "admin_only" = stc.admin_only) custom_commands[command_name] = stc - var/commands_file = cached_json["chat_commands_json"] + var/commands_file = chat_commands_json if(!commands_file) return text2file(json_encode(results), commands_file) diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 44a3b7baad..0d381b559b 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -14,10 +14,5 @@ namespace Tgstation.Server.Api.Models /// Git revision the compiler ran on. Not modifiable /// public RevisionInformation RevisionInformation { get; set; } - - /// - /// The the was made with - /// - public Version ByondVersion { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index 7f717c121c..6255f1591c 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -37,5 +37,10 @@ namespace Tgstation.Server.Api.Models.Internal /// Exit code of DM. If /// public int? ExitCode { get; set; } + + /// + /// The the was made with + /// + public Version ByondVersion { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByond.cs index 6af7e4627b..105d6dac74 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByond.cs @@ -24,13 +24,10 @@ namespace Tgstation.Server.Host.Components Task GetVersion(CancellationToken cancellationToken); /// - /// Lock the current installation's location and run an + /// Lock the current installation's location and return a /// - /// The return type of - /// A taking the path to either dm.exe and dreamdaemon.exe and returning a - /// Use the staged installation if possible - /// A resulting in the return type of - Task UseExecutables(Func> operation, bool stagedIfExists); + /// The BYOND required + IByondExecutableLock UseExecutables(Version requiredVersion); /// /// Clears the cache folder diff --git a/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs new file mode 100644 index 0000000000..d83f18c9ca --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs @@ -0,0 +1,12 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + public interface IByondExecutableLock : IDisposable + { + string DreamDaemonPath { get; set; } + string DreamMakerPath { get; set; } + + void DoNotDeleteThisSession(); + } +} diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/IChat.cs index a1f494a1ff..1035bdbbda 100644 --- a/src/Tgstation.Server.Host/Components/IChat.cs +++ b/src/Tgstation.Server.Host/Components/IChat.cs @@ -42,7 +42,7 @@ namespace Tgstation.Server.Host.Components /// Send a chat to a given set of /// /// The message being sent - /// The s of the s to send to + /// The s of the s to send to /// The for the operation /// A representing the running operation Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken); @@ -55,6 +55,6 @@ namespace Tgstation.Server.Host.Components /// The name of the chat commands json /// The for the operation /// A resulting in a tied to the lifetime of the json trackings - Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken); + Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IChatJsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/IChatJsonTrackingContext.cs new file mode 100644 index 0000000000..b2c9e53e57 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IChatJsonTrackingContext.cs @@ -0,0 +1,8 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + public interface IChatJsonTrackingContext : IDisposable + { + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs deleted file mode 100644 index 474beeeaf5..0000000000 --- a/src/Tgstation.Server.Host/Components/Watchdog.cs +++ /dev/null @@ -1,402 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Core; - -namespace Tgstation.Server.Host.Components -{ - /// - sealed class Watchdog : IWatchdog, IDisposable - { - /// - public Host.Models.CompileJob CurrentCompileJob { get; private set; } - - /// - /// The for the - /// - readonly IByond byond; - /// - /// The for the - /// - readonly IChat chat; - /// - /// The for the - /// - readonly IDreamDaemonExecutor dreamDaemonExecutor; - /// - /// The for the - /// - readonly IInterop interop; - /// - /// The for the - /// - readonly IDmbFactory dmbFactory; - /// - /// The for the - /// - readonly IEventConsumer eventConsumer; - /// - /// The for the - /// - readonly IInstanceManager instanceManager; - /// - /// The for the - /// - readonly IApplication application; - /// - /// The of the - /// - readonly long instanceId; - - /// - /// Represents the currently running - /// - Task watchdogTask; - /// - /// for - /// - CancellationTokenSource watchdogCancellationTokenSource; - - /// - /// Construct a - /// - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - /// The value of - public Watchdog(IByond byond, IChat chat, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInstanceManager instanceManager, IApplication application, long instanceId) - { - this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); - this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); - this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); - this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); - this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); - this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); - this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); - this.application = application ?? throw new ArgumentNullException(nameof(application)); - - this.instanceId = instanceId; - } - - /// - public void Dispose() => watchdogCancellationTokenSource?.Dispose(); - - /// - /// Loads a dmb and runs it through - /// - /// The for the run - /// The to be completed once the server starts if any - /// The the server - /// The for the operation - /// The path to the DreamDaemon executable - /// If the ports should be swapped - /// The for the operation - /// A resulting in the exit code of DreamDaemon - async Task RunServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, InteropInfo interopInfo, IInteropControl control, string dreamDaemonPath, bool asDefaultOtherServer, CancellationToken cancellationToken) - { - using (var dmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false)) - { - void OnBecomingCurrentServer() => CurrentCompileJob = dmb.CompileJob; - - var runningNow = launchParameters.SecondaryPort == interopInfo.NextPort; - if (asDefaultOtherServer) - runningNow = !runningNow; - - if (runningNow) - OnBecomingCurrentServer(); - else - control.OnNextSwap(OnBecomingCurrentServer); - - var chatJsonGuid = Guid.NewGuid(); - var isPrimary = interopInfo.NextPort == launchParameters.SecondaryPort; - - //set up chat stuff - interopInfo.ChatChannelsJson = String.Concat(chatJsonGuid, ".channels.json"); - //only track chat commands from primary server - if(isPrimary) - interopInfo.ChatCommandsJson = String.Concat(chatJsonGuid, ".commands.json"); - - //set up revision - interopInfo.Revision = dmb.CompileJob.RevisionInformation; - foreach (var I in dmb.CompileJob.RevisionInformation.TestMerges) - interopInfo.TestMerges.Add(new Models.TestMerge - { - Author = I.Author, - Body = I.BodyAtMerge, - Comment = I.Comment, - Commit = I.RevisionInformation.Commit, - OriginRevision = I.RevisionInformation.OriginRevision, - Number = I.Number, - PullRequestCommit = I.PullRequestRevision, - TimeMerged = I.MergedAt.ToUnixTimeSeconds(), - Title = I.TitleAtMerge, - Url = I.Url - }); - - using (await chat.TrackJsons(isPrimary ? dmb.PrimaryDirectory : dmb.SecondaryDirectory, interopInfo.ChatChannelsJson, isPrimary ? interopInfo.ChatCommandsJson : null, cancellationToken).ConfigureAwait(false)) - return await dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, dreamDaemonPath, dmb, interopInfo, false, asDefaultOtherServer, cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Locks in a version and runs a server through - /// - /// The for the run - /// The to be completed once the server starts if any - /// The for the operation - /// If a primary server is being launched - /// If the ports should be swapped - /// The for the operation - /// A tied to the lifetime of the resulting - /// A resulting in the exit code of DreamDaemon - Task StartServer(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource onSuccessfulStartup, IInteropControl control, bool isPrimary, bool isDefaultOther, CancellationToken cancellationToken, out CancellationTokenSource cancellationTokenSource) - { - cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - try - { - var ddToken = cancellationTokenSource.Token; - - var interopInfo = new InteropInfo - { - AccessToken = isPrimary ? control.PrimaryAccessToken : control.SecondaryAccessToken, - ApiValidateOnly = false, - HostPath = application.HostingPath, - InstanceId = instanceId, - NextPort = isPrimary ? launchParameters.SecondaryPort.Value : launchParameters.PrimaryPort.Value, - //this line feels hacky, change it and remove the instanceManager dep? - InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name, - }; - - return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, control, dreamDaemonPath, isDefaultOther, ddToken), false); - } - catch - { - cancellationTokenSource.Dispose(); - throw; - } - } - - /// - /// Handle a crash or exit of a server - /// - /// The resulting in the exit code of the server - /// If the ended server was the primary server - /// The for the operation - /// A resulting in if the server was ended due to a , otherwise - async Task HandleServerCrashed(Task serverTask, bool isPrimary, CancellationToken cancellationToken) - { - int exitCode; - try - { - //nothing to do except try and reboot it - exitCode = await serverTask.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - return true; - } - await eventConsumer.HandleEvent(exitCode == 0 ? (isPrimary ? EventType.DDExit : EventType.DDOtherExit) : (isPrimary ? EventType.DDCrash : EventType.DDOtherCrash), null, cancellationToken).ConfigureAwait(false); - return false; - } - - /// - /// Handle - /// - /// The - /// The for the operation - /// A representing the running operation - Task HandleChatMessage(ChatMessageEventArgs e, CancellationToken cancellationToken) - { - if (e == null) - throw new ArgumentNullException(nameof(e)); - return chat.SendMessage(e.ChatResponse.Message, e.ChatResponse.ChannelIds, cancellationToken); - } - - /// - /// Main loop - /// - /// The for the run - /// The to be completed once the first server starts - /// The for the operation - /// A representing the running operation - async Task Run(ILaunchParametersFactory launchParametersFactory, TaskCompletionSource onSuccessfulStartup, CancellationToken cancellationToken) - { - if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null) - throw new InvalidOperationException("No byond version installed!"); - await byond.ClearCache(cancellationToken).ConfigureAwait(false); - - var initialLaunchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); - var retries = 0; - do - { - var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour - await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); - - using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort.Value, initialLaunchParameters.SecondaryPort.Value, HandleChatMessage)) - { - var primaryPrimedTcs = new TaskCompletionSource(); - control.OnServerControl += (sender, e) => - { - if (e.FromPrimaryServer && e.EventType == ServerControlEventType.ServerPrimed) - primaryPrimedTcs.TrySetResult(null); - }; - - //start the primary server - var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control, true, false, cancellationToken, out CancellationTokenSource primaryCts); - try - { - //wait to make sure we got this far - await onSuccessfulStartup.Task.ConfigureAwait(false); - onSuccessfulStartup = null; - - //wait for either the server to exit or be primed - await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false); - - if (ddPrimaryTask.IsCompleted) - { - if (await HandleServerCrashed(ddPrimaryTask, true, cancellationToken).ConfigureAwait(false)) - return; - ++retries; - continue; - } - - var launchParameters = initialLaunchParameters; - Task ddSecondaryTask = null; - CancellationTokenSource secondaryCts = null; - try - { - do - { - if (ddSecondaryTask == null) - //start the secondary server - ddSecondaryTask = StartServer(initialLaunchParameters, null, control, false, false, cancellationToken, out secondaryCts); - - var newDmbTask = dmbFactory.OnNewerDmb(); - - //now we wait for something to happen - await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, newDmbTask).ConfigureAwait(false); - - //some helpers - void PrimaryRestart() - { - primaryCts.Dispose(); - ddPrimaryTask = StartServer(initialLaunchParameters, null, control, true, true, cancellationToken, out primaryCts); - } - void SecondaryRestart() - { - ddSecondaryTask = null; - secondaryCts.Dispose(); - }; - Task PrimaryCrash() => HandleServerCrashed(ddPrimaryTask, control.SecondaryIsOther, cancellationToken); - Task SecondaryCrash() => HandleServerCrashed(ddSecondaryTask, !control.SecondaryIsOther, cancellationToken); - - //update available - if (newDmbTask.IsCompleted) - { - //restart the other server but don't treat it as an error - launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); - //restart other server - if (control.SecondaryIsOther) - { - secondaryCts.Cancel(); - await ddSecondaryTask.ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - SecondaryRestart(); - } - else - { - primaryCts.Cancel(); - await ddPrimaryTask.ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - PrimaryRestart(); - } - continue; - } - - //crash of both servers - if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted) - { - //catastrophic, start over - var t1 = PrimaryCrash(); - await Task.WhenAll(t1, SecondaryCrash()).ConfigureAwait(false); - if (t1.Result) - return; - ++retries; - continue; - } - - //below this point: crash of single server - - //activate the other server and load new launch params - var otherServerActivation = control.ActivateOtherServer(cancellationToken); - launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false); - await otherServerActivation.ConfigureAwait(false); - - //crash of secondary server - if (ddSecondaryTask.IsCompleted) - { - if (await SecondaryCrash().ConfigureAwait(false)) - return; - SecondaryRestart(); - } - //crash of primary server - else - { - if (await PrimaryCrash().ConfigureAwait(false)) - return; - PrimaryRestart(); - } - } while (true); - } - finally - { - secondaryCts?.Dispose(); - } - } - finally - { - primaryCts.Dispose(); - } - } - } while (true); - } - - /// - public async Task Start(ILaunchParametersFactory launchParametersFactory, CancellationToken cancellationToken) - { - TaskCompletionSource taskCompletionSource; - lock (this) - { - if (watchdogTask != null) - throw new InvalidOperationException("Watchdog already running!"); - watchdogCancellationTokenSource?.Dispose(); - watchdogCancellationTokenSource = new CancellationTokenSource(); - taskCompletionSource = new TaskCompletionSource(); - watchdogTask = Run(launchParametersFactory, taskCompletionSource, watchdogCancellationTokenSource.Token); - } - - using (cancellationToken.Register(() => watchdogCancellationTokenSource.Cancel())) - await taskCompletionSource.Task.ConfigureAwait(false); - } - - /// - public Task Stop() - { - lock(this) - { - if (watchdogTask == null) - throw new InvalidOperationException("Watchdog not running!"); - watchdogCancellationTokenSource.Cancel(); - var task = watchdogTask; - watchdogTask = null; - return task; - } - } - } -} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs similarity index 95% rename from src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs rename to src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs index e793e3cbcc..c626d83ceb 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionManager.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Handles communication with a /// - interface ISessionManager : ISessionBase + interface ISessionController : ISessionBase { /// /// If the of is being used @@ -31,7 +31,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Releases the without terminating it. Also calls /// - /// which can be used to create a new similar to this one + /// which can be used to create a new similar to this one ReattachInformation Release(); /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs new file mode 100644 index 0000000000..49bcbcc383 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// Factory for s + /// + interface ISessionControllerFactory + { + Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken); + + Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionManagerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionManagerFactory.cs deleted file mode 100644 index 0dfa13b376..0000000000 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionManagerFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; - -namespace Tgstation.Server.Host.Components.Watchdog -{ - /// - /// Factory for s - /// - interface ISessionManagerFactory - { - ISessionManager LaunchNew(DreamDaemonLaunchParameters launchParameters); - - Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken); - } -} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index ed8ca4899b..18e6ef8dda 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -9,6 +9,6 @@ namespace Tgstation.Server.Host.Components.Watchdog { DreamDaemonLaunchParameters LaunchParameters { get; set; } - Task Launch(CancellationToken cancellationToken); + Task Launch(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/InteropInfo.cs b/src/Tgstation.Server.Host/Components/Watchdog/InteropInfo.cs index a68aaa5bf0..9565cb1cab 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/InteropInfo.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/InteropInfo.cs @@ -6,20 +6,14 @@ namespace Tgstation.Server.Host.Components.Watchdog { sealed class InteropInfo { - public Guid ExecutionId { get; set; } - - public string AccessToken { get; set; } - - public long InstanceId { get; set; } - + public string AccessIdentifier { get; set; } + public string HostPath { get; set; } public bool ApiValidateOnly { get; set; } public string InstanceName { get; set; } - - public ushort NextPort { get; set; } - + public string ChatChannelsJson { get; set; } public string ChatCommandsJson { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs index cc852a69fd..4cd93191ad 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { /// - /// Parameters necessary for duplicating a session + /// Parameters necessary for duplicating a session /// sealed class ReattachInformation { @@ -36,5 +36,15 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The used by DreamDaemon /// public IDmbProvider Dmb { get; set; } + + /// + /// Path to the chat commands json file + /// + public string ChatCommandsJson { get; set; } + + /// + /// Path to the chat channels json file + /// + public string ChatChannelsJson { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs index 61030ee4d4..5bd85ab417 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public int ProcessId => process.Id; /// - public Task Startup { get; } + public Task LaunchResult { get; } /// public Task Lifetime => lifetimeTask.Task; @@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { this.process = process ?? throw new ArgumentNullException(nameof(process)); - Startup = Task.Factory.StartNew(() => + LaunchResult = Task.Factory.StartNew(() => { var startTime = DateTimeOffset.Now; try diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionManager.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs similarity index 82% rename from src/Tgstation.Server.Host/Components/Watchdog/SessionManager.cs rename to src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index b2012b7873..469edb5396 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionManager.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class SessionManager : ISessionManager, IInteropConsumer + sealed class SessionController : ISessionController, IInteropConsumer { /// /// Generic OK response @@ -71,26 +71,42 @@ namespace Tgstation.Server.Host.Components.Watchdog } } + /// + public Task LaunchResult => session.LaunchResult; + + /// + public Task Lifetime => session.Lifetime; + /// /// The up to date /// readonly ReattachInformation reattachInformation; /// - /// The for the + /// The for the /// readonly IByondTopicSender byondTopicSender; /// - /// The for the + /// The for the /// readonly IInteropContext interopContext; /// - /// The for the + /// The for the /// readonly ISession session; + /// + /// The for the + /// + readonly IChatJsonTrackingContext chatJsonTrackingContext; + + /// + /// The for the + /// + readonly IChat chat; + /// /// The waits on when DreamDaemon currently has it's ports closed /// @@ -105,22 +121,26 @@ namespace Tgstation.Server.Host.Components.Watchdog /// bool portClosed; /// - /// If the has been disposed + /// If the has been disposed /// bool disposed; /// - /// Construct a + /// Construct a /// /// The value of /// The value of /// The value of /// The used to construct - public SessionManager(ReattachInformation reattachInformation, ISession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar) + /// The value of + /// The value of + public SessionController(ReattachInformation reattachInformation, ISession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, IChatJsonTrackingContext chatJsonTrackingContext, IChat chat) { + this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.session = session ?? throw new ArgumentNullException(nameof(session)); + this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); if (interopRegistrar == null) throw new ArgumentNullException(nameof(interopRegistrar)); @@ -140,6 +160,7 @@ namespace Tgstation.Server.Host.Components.Watchdog session.Dispose(); interopContext.Dispose(); Dmb?.Dispose(); //will be null when released + chatJsonTrackingContext.Dispose(); disposed = true; } } @@ -169,7 +190,7 @@ namespace Tgstation.Server.Host.Components.Watchdog void CheckDisposed() { if (disposed) - throw new ObjectDisposedException(nameof(SessionManager)); + throw new ObjectDisposedException(nameof(SessionController)); } /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs new file mode 100644 index 0000000000..81e1ecc9aa --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -0,0 +1,150 @@ +using Byond.TopicSender; +using Newtonsoft.Json; +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + sealed class SessionControllerFactory : ISessionControllerFactory + { + /// + /// The for the + /// + readonly IExecutor executor; + + /// + /// The for the + /// + readonly IByond byond; + + /// + /// The for the + /// + readonly IByondTopicSender byondTopicSender; + + /// + /// The for the + /// + readonly IInteropRegistrar interopRegistrar; + + /// + /// The for the + /// + readonly ICryptographySuite cryptographySuite; + + /// + /// The for the + /// + readonly IApplication application; + + /// + /// The for the + /// + readonly IInstance instance; + + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// The for the + /// + readonly IChat chat; + + /// + public async Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken) + { + var portToUse = primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort; + if (!portToUse.HasValue) + throw new InvalidOperationException("Given port is null!"); + var accessIdentifier = cryptographySuite.GetSecureString(); + + string GuidJsonFile() => String.Format(CultureInfo.InvariantCulture, "{0}.json", Guid.NewGuid()); + + var interopInfo = new InteropInfo + { + AccessIdentifier = accessIdentifier, + ApiValidateOnly = apiValidate, + ChatChannelsJson = GuidJsonFile(), + ChatCommandsJson = GuidJsonFile(), + HostPath = application.HostingPath, + InstanceName = instance.GetMetadata().Name, + Revision = dmbProvider.CompileJob.RevisionInformation + }; + interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.TestMerges.Select(x => new TestMerge + { + Author = x.Author, + Body = x.BodyAtMerge, + Comment = x.Comment, + Commit = x.RevisionInformation.Commit, + Number = x.Number, + OriginRevision = x.RevisionInformation.OriginRevision, + PullRequestCommit = x.PullRequestRevision, + TimeMerged = x.MergedAt.Ticks, + Title = x.TitleAtMerge, + Url = x.Url + })); + + var interopJsonFile = GuidJsonFile(); + + var interopJson = JsonConvert.SerializeObject(interopInfo); + + var basePath = primaryDirectory ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory; + var localIoManager = new ResolvingIOManager(ioManager, ioManager.ConcatPath(basePath, dmbProvider.DmbName)); + + var chatJsonTrackingTask = chat.TrackJsons(basePath, interopInfo.ChatChannelsJson, interopInfo.ChatCommandsJson, cancellationToken); + + await localIoManager.WriteAllBytes(interopJsonFile, Encoding.UTF8.GetBytes(interopJson), cancellationToken).ConfigureAwait(false); + var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false); + try + { + var byondLock = byond.UseExecutables(dmbProvider.CompileJob.ByondVersion); + try + { + var session = executor.RunDreamDaemon(launchParameters, byondLock, dmbProvider, /*TODO*/, !primaryPort, !primaryDirectory); + try + { + return new SessionController(new ReattachInformation + { + AccessIdentifier = accessIdentifier, + Dmb = dmbProvider, + IsPrimary = primaryDirectory, + Port = portToUse.Value, + ProcessId = session.ProcessId + }, session, byondTopicSender, interopRegistrar, chatJsonTrackingContext, chat); + } + catch + { + session.Dispose(); + throw; + } + } + catch + { + byondLock.Dispose(); + throw; + } + } + catch + { + chatJsonTrackingTask.Dispose(); + throw; + } + } + + /// + public Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 4bfb26a678..2acb52dfea 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -12,20 +12,15 @@ namespace Tgstation.Server.Host.Components.Watchdog set => launchParameters = value ?? throw new ArgumentNullException(nameof(value)); } - /// - /// The for the - /// - readonly IByond byond; - /// /// The for the /// readonly IChat chat; /// - /// The for the + /// The for the /// - readonly ISessionManagerFactory sessionManagerFactory; + readonly ISessionControllerFactory sessionManagerFactory; /// /// The for the @@ -44,21 +39,23 @@ namespace Tgstation.Server.Host.Components.Watchdog DreamDaemonLaunchParameters launchParameters; - ISessionManager alphaServer; - ISessionManager bravoServer; + ISessionController alphaServer; + ISessionController bravoServer; + + bool alphaActive; + + bool disposed; /// /// Construct a /// - /// The value of /// The value of /// The value of /// The value of /// The value of /// The value of - public Watchdog(IByond byond, IChat chat, ISessionManagerFactory sessionManagerFactory, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar, DreamDaemonLaunchParameters initialLaunchParameters) + public Watchdog(IByond byond, IChat chat, ISessionControllerFactory sessionManagerFactory, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar, DreamDaemonLaunchParameters initialLaunchParameters) { - this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); @@ -70,8 +67,12 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public void Dispose() { - alphaServer?.Dispose(); - bravoServer?.Dispose(); + lock (this) + { + alphaServer?.Dispose(); + bravoServer?.Dispose(); + disposed = true; + } } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index f7030c2dda..e11ce4cd29 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -17,9 +17,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly IChat chat; /// - /// The for the + /// The for the /// - readonly ISessionManagerFactory sessionManagerFactory; + readonly ISessionControllerFactory sessionManagerFactory; /// /// The for the @@ -39,7 +39,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public WatchdogFactory(IByond byond, IChat chat, ISessionManagerFactory sessionManagerFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar) + public WatchdogFactory(IByond byond, IChat chat, ISessionControllerFactory sessionManagerFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar) { this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); @@ -49,6 +49,6 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParamaters) => new Watchdog(byond, chat, sessionManagerFactory, dmbFactory, eventConsumer, interopRegistrar, launchParameters); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters) => new Watchdog(byond, chat, sessionManagerFactory, dmbFactory, eventConsumer, interopRegistrar, launchParameters); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs new file mode 100644 index 0000000000..3109a1ef2b --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs @@ -0,0 +1,23 @@ +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// Reattach information for a + /// + sealed class WatchdogReattachInformation + { + /// + /// If the Alpha session is the active session + /// + bool AlphaIsActive { get; set; } + + /// + /// for the Alpha session + /// + ReattachInformation Alpha { get; set; } + + /// + /// for the Bravo session + /// + ReattachInformation Bravo { get; set; } + } +} From 7842fcbbda9608e429428aea4725ce2f53bfce3e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 4 Jul 2018 17:02:29 -0400 Subject: [PATCH 172/397] Cleanup --- src/Tgstation.Server.Host.Service/Program.cs | 3 +- .../Components/DreamMaker.cs | 20 +++++-------- .../Components/IByondExecutableLock.cs | 13 ++++++++ .../Components/IInterop.cs | 16 ---------- .../Components/IWatchdog.cs | 30 ------------------- .../Components/ServerControlEvent.cs | 20 ------------- .../Components/ServerControlEventType.cs | 25 ---------------- .../Components/TemporaryDmbProvider.cs | 2 ++ .../Components/Watchdog/Executor.cs | 12 ++++---- .../Components/Watchdog/IExecutor.cs | 7 +++-- .../Components/Watchdog/Session.cs | 9 +++++- .../Watchdog/SessionControllerFactory.cs | 5 +++- .../Components/Watchdog/Watchdog.cs | 6 ++++ 13 files changed, 51 insertions(+), 117 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Components/IInterop.cs delete mode 100644 src/Tgstation.Server.Host/Components/IWatchdog.cs delete mode 100644 src/Tgstation.Server.Host/Components/ServerControlEvent.cs delete mode 100644 src/Tgstation.Server.Host/Components/ServerControlEventType.cs diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 59c11072a7..1a5a1a9dcf 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -47,10 +47,9 @@ namespace Tgstation.Server.Host.Service /// public void OnExecute() { - if((Install || Uninstall) && !IsAdministrator()) + if ((Install || Uninstall) && !IsAdministrator()) { //try to restart as admin - var currentProcess = Process.GetCurrentProcess(); var argList = Environment.GetCommandLineArgs().ToList(); //its windows, first arg is .exe name guaranteed var exe = argList.First(); diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index abecf25fe8..4bf02b7873 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; -using Tgstation.Server.Host.Components.Models; +using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components @@ -45,18 +45,14 @@ namespace Tgstation.Server.Host.Components /// readonly IConfiguration configuration; /// - /// The for + /// The for /// - readonly IDreamDaemonExecutor dreamDaemonExecutor; + readonly ISessionControllerFactory sessionControllerFactory; /// /// The for /// readonly IByond byond; /// - /// The for - /// - readonly IInterop interop; - /// /// The for /// readonly ICompileJobConsumer compileJobConsumer; @@ -76,13 +72,11 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// - public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICompileJobConsumer compileJobConsumer, IApplication application) + public DreamMaker(IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor)); - this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); - this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); + this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); this.application = application ?? throw new ArgumentNullException(nameof(application)); } @@ -91,10 +85,10 @@ namespace Tgstation.Server.Host.Components /// Run a quick DD instance to test the DMAPI is installed on the target code /// /// The path to the DreamDaemon executable - /// The for the operation + /// The for the operation /// The for the operation /// A resulting in if the DMAPI was successfully validated, otherwise - async Task VerifyApi(string dreamDaemonPath, Host.Models.CompileJob job, CancellationToken cancellationToken) + async Task VerifyApi(string dreamDaemonPath, Models.CompileJob job, CancellationToken cancellationToken) { var launchParameters = new DreamDaemonLaunchParameters { diff --git a/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs index d83f18c9ca..4da3b8eff8 100644 --- a/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs @@ -2,11 +2,24 @@ namespace Tgstation.Server.Host.Components { + /// + /// Represents usage of the two primary BYOND server executables + /// public interface IByondExecutableLock : IDisposable { + /// + /// The path to the DreamDaemon executable + /// string DreamDaemonPath { get; set; } + + /// + /// The path to the dm/DreamMaker executable + /// string DreamMakerPath { get; set; } + /// + /// Call if, during a detach, this version should not be deleted + /// void DoNotDeleteThisSession(); } } diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs deleted file mode 100644 index ff5337dc30..0000000000 --- a/src/Tgstation.Server.Host/Components/IInterop.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Tgstation.Server.Host.Components -{ - /// - /// Handles communication with the DMAPI - /// - interface IInterop - { - IInteropControl ReconnectToRun(string primaryAccessToken, string secondaryAccessToken, ushort primaryPort, ushort secondaryPort); - - IInteropControl CreateRun(ushort primaryPort, ushort? secondaryPort, Func chatMessageHandler); - } -} diff --git a/src/Tgstation.Server.Host/Components/IWatchdog.cs b/src/Tgstation.Server.Host/Components/IWatchdog.cs deleted file mode 100644 index 5504baa960..0000000000 --- a/src/Tgstation.Server.Host/Components/IWatchdog.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Tgstation.Server.Host.Components -{ - /// - /// For monitoring DreamDaemon uptime - /// - interface IWatchdog - { - /// - /// The latest used by the - /// - Host.Models.CompileJob CurrentCompileJob { get; } - - /// - /// Start the - /// - /// The for the run - /// The for the operation - /// A representing the running operation - Task Start(ILaunchParametersFactory launchParametersFactory, CancellationToken cancellationToken); - - /// - /// Stop the - /// - /// A representing the running operation - Task Stop(); - } -} diff --git a/src/Tgstation.Server.Host/Components/ServerControlEvent.cs b/src/Tgstation.Server.Host/Components/ServerControlEvent.cs deleted file mode 100644 index b619b68603..0000000000 --- a/src/Tgstation.Server.Host/Components/ServerControlEvent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Tgstation.Server.Host.Components -{ - /// - /// Represents a DreamDaemon control request or notification - /// - sealed class ServerControlEvent : EventArgs - { - /// - /// The - /// - public ServerControlEventType EventType { get; set; } - - /// - /// If the message was sent from the primary server - /// - public bool FromPrimaryServer { get; set; } - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventType.cs b/src/Tgstation.Server.Host/Components/ServerControlEventType.cs deleted file mode 100644 index 9468bcabaa..0000000000 --- a/src/Tgstation.Server.Host/Components/ServerControlEventType.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Tgstation.Server.Host.Components -{ - /// - /// Indicates a server control event from a DreamDaemon instance - /// - enum ServerControlEventType - { - /// - /// The server is loaded - /// - ServerPrimed, - /// - /// The server called world/Reboot() - /// - ServerRebooting, - /// - /// The server requested that it's process be terminated - /// - RequestedProcessTermination, - /// - /// The server has stopped responding to heartbeats - /// - ServerUnresponsive, - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs index fc4fa15408..de3c7164ac 100644 --- a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs @@ -33,5 +33,7 @@ namespace Tgstation.Server.Host.Components /// public void Dispose() { } + + public void KeepAlive() => throw new NotSupportedException(); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs index 718b459917..e31b223d2a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs @@ -30,15 +30,15 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public ISession AttachToDreamDaemon(int processId) => new Session(Process.GetProcessById(processId)); + public ISession AttachToDreamDaemon(int processId, IByondExecutableLock byondLock) => new Session(Process.GetProcessById(processId), byondLock); /// - public ISession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory) + public ISession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, IByondExecutableLock byondLock, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory) { if (launchParameters == null) throw new ArgumentNullException(nameof(launchParameters)); - if (dreamDaemonPath == null) - throw new ArgumentNullException(nameof(dreamDaemonPath)); + if (byondLock == null) + throw new ArgumentNullException(nameof(byondLock)); if (dmbProvider == null) throw new ArgumentNullException(nameof(dmbProvider)); if (parameters == null) @@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Components.Watchdog try { - proc.StartInfo.FileName = dreamDaemonPath; + proc.StartInfo.FileName = byondLock.DreamDaemonPath; proc.StartInfo.WorkingDirectory = useSecondaryDirectory ? dmbProvider.SecondaryDirectory : dmbProvider.PrimaryDirectory; proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}\"", @@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Components.Watchdog proc.Start(); - return new Session(proc); + return new Session(proc, byondLock); } catch { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs b/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs index 5555d24030..b27a06a78f 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs @@ -12,18 +12,19 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The /// The that is completed when dream daemon starts without crashing - /// The path to the dreamdaemon executable + /// The for the new /// The for the .dmb to run /// The value of the -params command line option /// If the field of should be used /// A new - ISession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, string dreamDaemonPath, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory); + ISession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, IByondExecutableLock byondLock, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory); /// /// Attach to a running instance of DreamDaemon /// /// The + /// The for the new /// A new - ISession AttachToDreamDaemon(int processId); + ISession AttachToDreamDaemon(int processId, IByondExecutableLock byondLock); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs index 5bd85ab417..e978b66003 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs @@ -20,6 +20,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The actual /// readonly Process process; + /// + /// The for the + /// + readonly IByondExecutableLock byondLock; + /// /// The backing for /// @@ -29,9 +34,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Construct a /// /// The value of - public Session(Process process) + /// The value of + public Session(Process process, IByondExecutableLock byondLock) { this.process = process ?? throw new ArgumentNullException(nameof(process)); + this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock)); LaunchResult = Task.Factory.StartNew(() => { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 81e1ecc9aa..aae84ea84a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -110,7 +110,10 @@ namespace Tgstation.Server.Host.Components.Watchdog var byondLock = byond.UseExecutables(dmbProvider.CompileJob.ByondVersion); try { - var session = executor.RunDreamDaemon(launchParameters, byondLock, dmbProvider, /*TODO*/, !primaryPort, !primaryDirectory); + var parameters = String.Format(CultureInfo.InvariantCulture, "server_service_version={0}&tgs_json={1}", application.Version, interopJsonFile); + + + var session = executor.RunDreamDaemon(launchParameters, byondLock, dmbProvider, parameters, !primaryPort, !primaryDirectory); try { return new SessionController(new ReattachInformation diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 2acb52dfea..4152b8bfb4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Watchdog @@ -6,6 +8,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// sealed class Watchdog : IWatchdog { + /// public DreamDaemonLaunchParameters LaunchParameters { get => launchParameters; @@ -74,5 +77,8 @@ namespace Tgstation.Server.Host.Components.Watchdog disposed = true; } } + + /// + public Task Launch(CancellationToken cancellationToken) => throw new NotImplementedException(); } } From 3affb50d8e30edc5bf36dfc291ed07b645ee8dec Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Jul 2018 10:17:41 -0400 Subject: [PATCH 173/397] I'm not angry --- src/DMAPI/tgs/v4/api.dm | 20 ++++++++++++++++++- .../Components/DreamMaker.cs | 8 +++++++- .../Components/Watchdog/LaunchResult.cs | 5 +++++ .../Controllers/InteropController.cs | 9 ++++++--- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index b5991119d4..8b65335eb8 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -121,7 +121,25 @@ sleep(10) /datum/tgs_api/v4/proc/Export(command) - return file2text(world.Export("[host_path]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]")["CONTENT"]) + var/list/res = world.Export("[host_path]/Interop?access=[url_encode(access_identifier)]&command=[url_encode(command)]") + if(!istype(res)) + TGS_ERROR_LOG("Error contacting TGS webapi at [host_path]! Export returned something not a list (probably null): [res]") + return + var/byond_status = res["STATUS"] + var/byond_content = res["CONTENT"] + if(byond_status != "200 OK") + TGS_ERROR_LOG("Error contacting TGS webapi at [host_path]! Top level HTTP [byond_status]: [byond_content]") + return + var/json = json_decode(byond_content) //always expecting json + if(!json) + TGS_ERROR_LOG("Error contacting TGS webapi at [host_path]! Byond content not json: [byond_content]") + return + var/status = json["STATUS"] + var/content = json["CONTENT"] + if(status != 200) //HTTP OK + TGS_ERROR_LOG("Error contacting TGS webapi at [host_path]! HTTP [status]: [json_encode(content)]") + return + return content /datum/tgs_api/v4/OnReboot() var/json = Export(TGS4_COMM_SERVER_REBOOT) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 4bf02b7873..d4747d8834 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -97,6 +97,13 @@ namespace Tgstation.Server.Host.Components SecurityLevel = DreamDaemonSecurity.Safe //all it needs to read the file and exit }; + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var provider = new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))); + using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, true, true, true, cancellationToken).ConfigureAwait(false)) + { + + } + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) using (var control = interop.CreateRun(launchParameters.PrimaryPort.Value, null, null)) { @@ -112,7 +119,6 @@ namespace Tgstation.Server.Host.Components if (e.EventType == ServerControlEventType.ServerUnresponsive) ddTcs.SetResult(null); }; - var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, false, cts.Token); await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs index 8f8ae755ab..12704584fb 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs @@ -7,6 +7,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// sealed class LaunchResult { + /// + /// If the DMAPI was validated + /// + public bool? ApiValidated { get; set; } + /// /// The time it took for to return /// diff --git a/src/Tgstation.Server.Host/Controllers/InteropController.cs b/src/Tgstation.Server.Host/Controllers/InteropController.cs index 937889b482..2f1690884d 100644 --- a/src/Tgstation.Server.Host/Controllers/InteropController.cs +++ b/src/Tgstation.Server.Host/Controllers/InteropController.cs @@ -39,9 +39,12 @@ namespace Tgstation.Server.Host.Controllers { var result = await instanceManager.HandleWorldExport(Request.Query, cancellationToken).ConfigureAwait(false); //explain things in very simple terms dream daemon can understand - if (result != null) - return Json(result); - return NotFound(); + //EXCEPT DREAMDAENEN BUGS + //THAT"S RIGHT !BUGS! + //ON ANYTHING THAT ISNT A 200 RESPOSNCE REEEE + if (result == null) + result = new { STATUS = 404 }; + return Json(result); } } } From 916139f1a6338b5b878b20983c2767a201c6c664 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Jul 2018 11:04:54 -0400 Subject: [PATCH 174/397] Bring DreamMaker back up to snuff --- src/DMAPI/tgs/v4/api.dm | 43 +++++--- .../Internal/DreamDaemonLaunchParameters.cs | 7 ++ .../Rights/DreamDaemonRights.cs | 4 +- .../Components/DreamMaker.cs | 51 ++++----- .../Components/IByondExecutableLock.cs | 9 +- .../Components/IDreamMaker.cs | 3 +- .../Components/Watchdog/ISessionController.cs | 5 + .../Components/Watchdog/LaunchResult.cs | 5 - .../Components/Watchdog/SessionController.cs | 101 +++++++++++++----- .../Controllers/DreamMakerController.cs | 4 +- 10 files changed, 147 insertions(+), 85 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 8b65335eb8..8622f49255 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -1,26 +1,34 @@ #define TGS4_PARAM_INFO_JSON "tgs_json" -#define TGS4_TOPIC_COMMAND "tgs_com" -#define TGS4_TOPIC_TOKEN "tgs_tok" -#define TGS4_TOPIC_SUCCESS "tgs_succ" -#define TGS4_TOPIC_SWAP "tgs_swap" -#define TGS4_TOPIC_SWAP_DELAYED "tgs_swap_delayed" +#define TGS4_INTEROP_ACCESS_IDENTIFIER "tgs_tok" + +#define TGS4_RESPONSE_SUCCESS "tgs_succ" + +#define TGS4_TOPIC_CHANGE_PORT "tgs_port" +#define TGS4_TOPIC_CHANGE_REBOOT_MODE "tgs_rmode" #define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm" #define TGS4_TOPIC_EVENT "tgs_event" -#define TGS4_TOPIC_IDENTIFY "tgs_ident" +#define TGS4_COMM_IDENTIFY "tgs_ident" #define TGS4_COMM_VALIDATE "tgs_vali" #define TGS4_COMM_SERVER_PRIMED "tgs_prime" -#define TGS4_COMM_SERVER_REBOOT "tgs_reboot" +#define TGS4_COMM_WORLD_REBOOT "tgs_reboot" #define TGS4_COMM_END_PROCESS "tgs_kill" #define TGS4_COMM_CHAT "tgs_chat_send" +#define TGS4_PARAMETER_COMMAND "tgs_com" +#define TGS4_PARAMETER_DATA "tgs_data" + +#define TGS4_PARAMETER_NEW_PORT "new_port" +#define TGS4_PARAMETER_NEW_REBOOT_MODE "new_rmode" + /datum/tgs_api/v4 var/access_identifier var/instance_name var/host_path var/chat_channels_json_path var/chat_commands_json_path + var/reboot_mode = TGS_REBOOT_MODE_NORMAL var/list/intercepted_message_queue @@ -120,24 +128,27 @@ while(!world.OpenPort(new_port)) sleep(10) -/datum/tgs_api/v4/proc/Export(command) - var/list/res = world.Export("[host_path]/Interop?access=[url_encode(access_identifier)]&command=[url_encode(command)]") +/datum/tgs_api/v4/proc/Export(command, list/data) + var/url = "[host_path]/Interop?[TGS4_INTEROP_ACCESS_IDENTIFIER]=[url_encode(access_identifier)]&[TGS4_PARAMETER_COMMAND]=[url_encode(command)]" + if(data) + url = "[url]&[TGS4_PARAMETER_DATA]=[url_encode(json_encode(data))]" + var/list/res = world.Export(url) if(!istype(res)) - TGS_ERROR_LOG("Error contacting TGS webapi at [host_path]! Export returned something not a list (probably null): [res]") + TGS_ERROR_LOG("Error contacting TGS webapi at [host_path] (command: [command])! world.Export returned something not a list (probably null): [res]") return var/byond_status = res["STATUS"] var/byond_content = res["CONTENT"] if(byond_status != "200 OK") - TGS_ERROR_LOG("Error contacting TGS webapi at [host_path]! Top level HTTP [byond_status]: [byond_content]") + TGS_ERROR_LOG("Error contacting TGS webapi at [host_path] (command: [command])! Top level HTTP [byond_status]: [byond_content]") return var/json = json_decode(byond_content) //always expecting json if(!json) - TGS_ERROR_LOG("Error contacting TGS webapi at [host_path]! Byond content not json: [byond_content]") + TGS_ERROR_LOG("Error contacting TGS webapi at [host_path] (command: [command])! Byond content not json: [byond_content]") return var/status = json["STATUS"] var/content = json["CONTENT"] if(status != 200) //HTTP OK - TGS_ERROR_LOG("Error contacting TGS webapi at [host_path]! HTTP [status]: [json_encode(content)]") + TGS_ERROR_LOG("Error contacting TGS webapi at [host_path] (command: [command])! HTTP [status]: [json_encode(content)]") return return content @@ -195,21 +206,21 @@ if(intercepted_message_queue) intercepted_message_queue += list(message) else - Export("[TGS4_COMM_CHAT] [json_encode(message)]") + Export(TGS4_COMM_CHAT, message) /datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) message = list("message" = message, "channels" = admin_only ? "admin" : "game") if(intercepted_message_queue) intercepted_message_queue += list(message) else - Export("[TGS4_COMM_CHAT] [json_encode(message)]") + Export(TGS4_COMM_CHAT, message) /datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) message = list("message" = message, "user" = list("id" = user.id, "channel" = user.channel.id)) if(intercepted_message_queue) intercepted_message_queue += list(message) else - Export("[TGS4_COMM_CHAT] [json_encode(message)]") + Export(TGS4_COMM_CHAT, message) /datum/tgs_api/v4/ChatChannelInfo() . = list() diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index 62e0f7eb87..f52315595f 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -36,5 +36,12 @@ namespace Tgstation.Server.Api.Models.Internal [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)] [Required] public ushort? SecondaryPort { get; set; } + + /// + /// The DreamDaemon startup timeout in seconds + /// + [Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetStartupTimeout)] + [Required] + public int? StartupTimeout { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index 5fbce5f145..cd18f5ba53 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -55,6 +55,8 @@ namespace Tgstation.Server.Api.Rights /// /// User can start and disable and /// - Start = 1024 + Start = 1024, + + SetStartupTimeout = 2048 } } diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index d4747d8834..173332c7d7 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -85,48 +85,34 @@ namespace Tgstation.Server.Host.Components /// Run a quick DD instance to test the DMAPI is installed on the target code /// /// The path to the DreamDaemon executable + /// The timeout in seconds for validation /// The for the operation /// The for the operation /// A resulting in if the DMAPI was successfully validated, otherwise - async Task VerifyApi(string dreamDaemonPath, Models.CompileJob job, CancellationToken cancellationToken) + async Task VerifyApi(string dreamDaemonPath, int timeout, Models.CompileJob job, CancellationToken cancellationToken) { var launchParameters = new DreamDaemonLaunchParameters { AllowWebClient = false, PrimaryPort = 0, //pick any port - SecurityLevel = DreamDaemonSecurity.Safe //all it needs to read the file and exit + SecurityLevel = DreamDaemonSecurity.Safe, //all it needs to read the file and exit + StartupTimeout = timeout }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); var provider = new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))); + + var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout); using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, true, true, true, cancellationToken).ConfigureAwait(false)) { + var timeoutTask = Task.Delay(timeoutAt - DateTimeOffset.Now, cancellationToken); - } + await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false); - using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) - using (var control = interop.CreateRun(launchParameters.PrimaryPort.Value, null, null)) - { - var interopInfo = new InteropInfo - { - ApiValidateOnly = true, - HostPath = application.HostingPath, - AccessToken = control.PrimaryAccessToken - }; - var ddTcs = new TaskCompletionSource(); - control.OnServerControl += (sender, e) => - { - if (e.EventType == ServerControlEventType.ServerUnresponsive) - ddTcs.SetResult(null); - }; - var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, false, cts.Token); + if (!controller.Lifetime.IsCompleted) + return false; - await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false); - - if (!ddTestTask.IsCompleted) - cts.Cancel(); - - return await ddTestTask.ConfigureAwait(false) == 0 && !ddTcs.Task.IsCompleted; + return controller.ApiValidated; } } @@ -224,12 +210,12 @@ namespace Tgstation.Server.Host.Components } /// - public async Task Compile(string projectName, IRepository repository, CancellationToken cancellationToken) + public async Task Compile(string projectName, int apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) { try { Status = CompilerStatus.Copying; - var job = new Host.Models.CompileJob + var job = new Models.CompileJob { DirectoryName = Guid.NewGuid(), DmeName = projectName @@ -272,14 +258,17 @@ namespace Tgstation.Server.Host.Components Status = CompilerStatus.Compiling; //run compiler, verify api - var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + bool ddVerified; + using (var byondLock = byond.UseExecutables(null)) { - await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + job.ByondVersion = byondLock.Version; + + await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false); Status = CompilerStatus.Verifying; - return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); - }, true).ConfigureAwait(false); + ddVerified = job.ExitCode == 0 && await VerifyApi(byondLock.DreamDaemonPath, apiValidateTimeout, job, cancellationToken).ConfigureAwait(false); + } if (!ddVerified) //server never validated or compile failed diff --git a/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs index 4da3b8eff8..2d390534e2 100644 --- a/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs @@ -7,15 +7,20 @@ namespace Tgstation.Server.Host.Components /// public interface IByondExecutableLock : IDisposable { + /// + /// The of the locked executables + /// + Version Version { get; } + /// /// The path to the DreamDaemon executable /// - string DreamDaemonPath { get; set; } + string DreamDaemonPath { get; } /// /// The path to the dm/DreamMaker executable /// - string DreamMakerPath { get; set; } + string DreamMakerPath { get; } /// /// Call if, during a detach, this version should not be deleted diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index cbbfb1deb9..55ebb664ff 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -18,9 +18,10 @@ namespace Tgstation.Server.Host.Components /// Starts a compile /// /// The name of the .dme to compile without the extension + /// The time in seconds to wait while validating the API /// The to copy from /// The for the operation /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated - Task Compile(string projectName, IRepository repository, CancellationToken cancellationToken); + Task Compile(string projectName, int apiValidateTimeout, IRepository repository, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs index c626d83ceb..af91fe2d32 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs @@ -13,6 +13,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// bool IsPrimary { get; } + /// + /// If the DMAPI was validated. This field may only be access once completes + /// + bool ApiValidated { get; } + /// /// The being used /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs index 12704584fb..8f8ae755ab 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs @@ -7,11 +7,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// sealed class LaunchResult { - /// - /// If the DMAPI was validated - /// - public bool? ApiValidated { get; set; } - /// /// The time it took for to return /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 469edb5396..90a8782bd3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -1,7 +1,10 @@ using Byond.TopicSender; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using System; +using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -11,23 +14,33 @@ namespace Tgstation.Server.Host.Components.Watchdog /// sealed class SessionController : ISessionController, IInteropConsumer { - /// - /// Generic OK response - /// - const string DMResponseOKGeneric = "OK"; + //interop values, match them up with tgs.dm and the appropriate api.dm - /// - /// The server is requesting to know what port to open on - /// - const string DMQueryPortsClosed = "its_dark"; + //api version 4.0.0.0 + const string DMParamInfoJson = "tgs_json"; + + const string DMInteropAccessIdentifier = "tgs_tok"; + + const string DMResponseSuccess = "tgs_succ"; + + const string DMTopicChangePort = "tgs_port"; + const string DMTopicChangeReboot = "tgs_rmode"; + const string DMTopicChatCommand = "tgs_chat_comm"; + const string DMTopicEvent = "tgs_event"; + + const string DMCommandIdentify = "tgs_ident"; + const string DMCommandApiValidate = "tgs_validate"; + const string DMCommandServerPrimed = "tgs_prime"; + const string DMCommandWorldReboot = "tgs_reboot"; + const string DMCommandEndProcess = "tgs_kill"; + const string DMCommandChat = "tgs_chat_send"; + + const string DMParameterCommand = "tgs_com"; + const string DMParameterData = "tgs_data"; - const string DMParameterAccessIdentifier = "access"; - const string DMParameterCommand = "command"; const string DMParameterNewPort = "new_port"; - const string DMParameterNewRebootMode = "new_reboot_mode"; + const string DMParameterNewRebootMode = "new_rmode"; - const string DMCommandChangePort = "change_port"; - const string DMCommandChangeReboot = "change_reboot"; /// public bool IsPrimary @@ -39,6 +52,17 @@ namespace Tgstation.Server.Host.Components.Watchdog } } + /// + public bool ApiValidated + { + get + { + if (!Lifetime.IsCompleted) + throw new InvalidOperationException("ApiValidated cannot be checked while Lifetime is incomplete!"); + return apiValidated; + } + } + /// public IDmbProvider Dmb { @@ -125,6 +149,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// bool disposed; + /// + /// If the DMAPI was validated + /// + bool apiValidated; + /// /// Construct a /// @@ -148,6 +177,7 @@ namespace Tgstation.Server.Host.Components.Watchdog portClosed = false; disposed = false; + apiValidated = false; } /// @@ -170,18 +200,33 @@ namespace Tgstation.Server.Host.Components.Watchdog { if (query == null) throw new ArgumentNullException(nameof(query)); - - if (query.ContainsKey(DMQueryPortsClosed)) - lock (this) - if(nextPort.HasValue) - { - var newPort = nextPort.Value; - nextPort = null; - portAssignmentTcs.SetResult(true); - portAssignmentTcs = null; - return Task.FromResult(new { new_port = newPort }); - } - return Task.FromResult(new object()); + + object content = new object(); + var status = HttpStatusCode.OK; + + if (query.TryGetValue(DMParameterCommand, out StringValues values)) + switch (values.First()) + { + case DMCommandIdentify: + lock (this) + if (nextPort.HasValue) + { + var newPort = nextPort.Value; + nextPort = null; + portAssignmentTcs.SetResult(true); + portAssignmentTcs = null; + content = new Dictionary { { DMParameterNewPort, newPort } }; + } + break; + case DMCommandApiValidate: + apiValidated = true; + break; + default: + status = HttpStatusCode.BadRequest; + content = new { message = "Requested command not supported!" }; + break; + } + return Task.FromResult(new { STATUS = status, CONTENT = content }); } /// @@ -207,9 +252,9 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic(new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), String.Format(CultureInfo.InvariantCulture, "?{0}={1}&{2}={3}", DMParameterAccessIdentifier, reattachInformation.AccessIdentifier, DMParameterCommand, command), cancellationToken); + public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic(new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), String.Format(CultureInfo.InvariantCulture, "?{0}={1}&{2}={3}", DMInteropAccessIdentifier, reattachInformation.AccessIdentifier, DMParameterCommand, command), cancellationToken); - async Task SetPortImpl(ushort port, CancellationToken cancellationToken) => await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", DMCommandChangePort, DMParameterNewPort, port), cancellationToken).ConfigureAwait(false) == DMResponseOKGeneric; + async Task SetPortImpl(ushort port, CancellationToken cancellationToken) => await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", DMTopicChangePort, DMParameterNewPort, port), cancellationToken).ConfigureAwait(false) == DMResponseSuccess; /// public async Task ClosePort(CancellationToken cancellationToken) @@ -252,7 +297,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (RebootState == newRebootState) return true; - return await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", DMCommandChangeReboot, DMParameterNewRebootMode, (int)newRebootState), cancellationToken).ConfigureAwait(false) == DMResponseOKGeneric; + return await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", DMTopicChangeReboot, DMParameterNewRebootMode, (int)newRebootState), cancellationToken).ConfigureAwait(false) == DMResponseSuccess; } } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index acb861adff..3e5844e7ef 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -112,7 +112,9 @@ namespace Tgstation.Server.Host.Controllers var instanceManager = serviceProvider.GetRequiredService(); var databaseContext = serviceProvider.GetRequiredService(); + var timeoutTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => x.StartupTimeout).FirstAsync(cancellationToken); var projectName = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => x.ProjectName).FirstAsync(cancellationToken).ConfigureAwait(false); + var timeout = await timeoutTask.ConfigureAwait(false); var instance = instanceManager.GetInstance(instanceModel); @@ -121,7 +123,7 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await instance.RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { revInfoTask = databaseContext.RevisionInformations.Where(x => x.Commit == repo.Head).Select(x => new RevisionInformation { Id = x.Id }).FirstAsync(); - compileJob = await instance.DreamMaker.Compile(projectName, repo, cancellationToken).ConfigureAwait(false); + compileJob = await instance.DreamMaker.Compile(projectName, timeout.Value, repo, cancellationToken).ConfigureAwait(false); } compileJob.Job = job; From bdaa8b9a6099bfcc5c0750044552e85eef7f9a6d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Jul 2018 11:50:36 -0400 Subject: [PATCH 175/397] All that's left is to write the watchdog --- .../Models/DreamDaemon.cs | 16 +- .../Components/DreamDaemon.cs | 216 ------------------ .../Components/IDreamDaemon.cs | 93 -------- .../Components/IInstance.cs | 5 +- .../Components/ILaunchParametersFactory.cs | 19 -- .../Components/Instance.cs | 16 +- .../Components/Watchdog/IWatchdog.cs | 74 +++++- .../Components/Watchdog/LaunchResult.cs | 2 +- .../Components/Watchdog/RebootState.cs | 2 +- .../Components/Watchdog/Watchdog.cs | 3 - .../Watchdog/WatchdogLaunchResult.cs | 17 +- .../Controllers/DreamDaemonController.cs | 58 +++-- 12 files changed, 136 insertions(+), 385 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Components/DreamDaemon.cs delete mode 100644 src/Tgstation.Server.Host/Components/IDreamDaemon.cs delete mode 100644 src/Tgstation.Server.Host/Components/ILaunchParametersFactory.cs diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index 4a332b36dd..3ca0f22d87 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -9,10 +9,16 @@ namespace Tgstation.Server.Api.Models public sealed class DreamDaemon : DreamDaemonSettings { /// - /// When the live revision was compiled + /// The live revision /// [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] - public CompileJob CompileJob { get; set; } + public CompileJob ActiveCompileJob { get; set; } + + /// + /// The next revision to go live + /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)] + public CompileJob StagedCompileJob { get; set; } /// /// The current status of @@ -31,5 +37,11 @@ namespace Tgstation.Server.Api.Models /// [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] public ushort? CurrentPort { get; set; } + + /// + /// The webclient status the running instance is set to + /// + [Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)] + public bool? CurrentAllowWebclient { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs deleted file mode 100644 index f699150079..0000000000 --- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Models.Internal; - -namespace Tgstation.Server.Host.Components -{ - /// - sealed class DreamDaemon : IDreamDaemon, ILaunchParametersFactory, IDisposable - { - /// - public bool Running { get; private set; } - - /// - public ushort? CurrentPort { get; private set; } - - /// - public string AccessToken { get; private set; } - - /// - public DreamDaemonSecurity? CurrentSecurity { get; private set; } - - /// - public bool SoftRebooting { get; private set; } - - /// - public bool SoftStopping { get; private set; } - - /// - public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; } - - /// - public Host.Models.CompileJob LastCompileJob => watchdog.CurrentCompileJob; - - /// - /// The for - /// - readonly IEventConsumer eventConsumer; - /// - /// The for - /// - readonly IInterop interop; - /// - /// The for - /// - readonly IWatchdog watchdog; - - /// - /// Used for write control to class variables - /// - readonly SemaphoreSlim semaphore; - - /// - /// If should start DD - /// - readonly bool autoStart; - - /// - /// The current - /// - DreamDaemonLaunchParameters currentLaunchParameters; - - /// - /// Construct - /// - /// The value of - /// The value of - /// The value of - /// The initial value of and - public DreamDaemon(IEventConsumer eventConsumer, IInterop interop, IWatchdog watchdog, DreamDaemonSettings initialSettings) - { - this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); - this.interop = interop ?? throw new ArgumentNullException(nameof(interop)); - this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); - currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings)); - - autoStart = initialSettings.AutoStart.Value; - - semaphore = new SemaphoreSlim(1); - } - - /// - public void Dispose() => semaphore.Dispose(); - - /// - public async Task CancelGracefulActions(CancellationToken cancellationToken) - { - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - SoftRebooting = false; - SoftStopping = false; - } - finally - { - semaphore.Release(); - } - } - - /// - public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) - { - if (launchParameters == null) - throw new ArgumentNullException(nameof(launchParameters)); - Task launchTask; - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - currentLaunchParameters = launchParameters; - if (!Running) - launchTask = Launch(launchParameters, cancellationToken); - else - launchTask = Restart(true, cancellationToken); - } - finally - { - semaphore.Release(); - } - await launchTask.ConfigureAwait(false); - } - - /// - public async Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) - { - if (launchParameters == null) - throw new ArgumentNullException(nameof(launchParameters)); - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - Task watchdogStartup; - try - { - if (Running) - throw new InvalidOperationException("DreamDaemon already running!"); - Running = true; - await eventConsumer.HandleEvent(EventType.DDLaunched, null, cancellationToken).ConfigureAwait(false); - watchdogStartup = watchdog.Start(this, cancellationToken); - } - finally - { - semaphore.Release(); - } - //important to leave the lock so the watchdog can enter it - await watchdogStartup.ConfigureAwait(false); - } - - /// - public async Task Restart(bool graceful, CancellationToken cancellationToken) - { - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - if (graceful) - { - if (!Running) - throw new InvalidOperationException("DreamDaemon not running!"); - if (SoftStopping) - throw new InvalidOperationException("DreamDaemon has a graceful stop queued!"); - SoftRebooting = true; - return; - } - await eventConsumer.HandleEvent(EventType.DDRestart, null, cancellationToken).ConfigureAwait(false); - if (Running) - await watchdog.Stop().ConfigureAwait(false); - await Launch(currentLaunchParameters, cancellationToken).ConfigureAwait(false); - } - finally - { - semaphore.Release(); - } - } - - /// - public Task StartAsync(CancellationToken cancellationToken) => autoStart ? Launch(currentLaunchParameters, cancellationToken) : Task.CompletedTask; - - /// - public Task StopAsync(CancellationToken cancellationToken) => Terminate(false, cancellationToken); - - /// - public async Task Terminate(bool graceful, CancellationToken cancellationToken) - { - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - if (graceful) - { - if (!Running) - throw new InvalidOperationException("DreamDaemon not running!"); - SoftRebooting = false; - SoftStopping = true; - return; - } - await eventConsumer.HandleEvent(EventType.DDTerminated, null, cancellationToken).ConfigureAwait(false); - await watchdog.Stop().ConfigureAwait(false); - } - finally - { - semaphore.Release(); - } - } - - /// - public async Task GetLaunchParameters(CancellationToken cancellationToken) - { - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - LastLaunchParameters = currentLaunchParameters; - return currentLaunchParameters; - } - finally - { - semaphore.Release(); - } - } - } -} diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs deleted file mode 100644 index e435a3af69..0000000000 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Microsoft.Extensions.Hosting; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Models.Internal; - -namespace Tgstation.Server.Host.Components -{ - /// - /// For managing DreamDaemon - /// - public interface IDreamDaemon : IHostedService - { - /// - /// If DreamDaemon is running - /// - bool Running { get; } - - /// - /// If DreamDaemon is going to soft reboot - /// - bool SoftRebooting { get; } - - /// - /// If DreamDaemon is going to soft stop - /// - bool SoftStopping { get; } - - /// - /// The port DreamDaemon is currently running on - /// - ushort? CurrentPort { get; } - - /// - /// The current of - /// - DreamDaemonSecurity? CurrentSecurity { get; } - - /// - /// The access token used for communication with the DMAPI - /// - string AccessToken { get; } - - /// - /// The most recently used - /// - DreamDaemonLaunchParameters LastLaunchParameters { get; } - - /// - /// The most recently used - /// - Host.Models.CompileJob LastCompileJob { get; } - - /// - /// Launch DreamDaemon - /// - /// The for the launch - /// The for the operation - /// A representing the running operation - Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken); - - /// - /// Changes the if currently . Triggers a graceful restart - /// - /// The new - /// The for the operation - /// A representing the running operation - Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken); - - /// - /// Restarts DreamDaemon - /// - /// If the restart will be delayed until a reboot is detected in the DMAPI and this function will retrun immediately. If the DMAPI isn't installed, this parameter is ignored - /// The for the operation - /// A representing the running operation - Task Restart(bool graceful, CancellationToken cancellationToken); - - /// - /// Terminates DreamDaemon - /// - /// If the termination will be delayed until a reboot is detected in the DMAPI and this function will return immediately. If the DMAPI isn't installed, this parameter is ignored - /// The for the operation - /// A representing the running operation - Task Terminate(bool graceful, CancellationToken cancellationToken); - - /// - /// Cancels any pending graceful reboots or terminations - /// - /// The for the operation - /// A representing the running operation - Task CancelGracefulActions(CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index a06c30c3a3..faa3c99de7 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Hosting; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Watchdog; namespace Tgstation.Server.Host.Components { @@ -24,9 +25,9 @@ namespace Tgstation.Server.Host.Components IDreamMaker DreamMaker { get; } /// - /// The for the + /// The for the /// - IDreamDaemon DreamDaemon { get; } + IWatchdog Watchdog { get; } /// /// The for the diff --git a/src/Tgstation.Server.Host/Components/ILaunchParametersFactory.cs b/src/Tgstation.Server.Host/Components/ILaunchParametersFactory.cs deleted file mode 100644 index ee2a67ec21..0000000000 --- a/src/Tgstation.Server.Host/Components/ILaunchParametersFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; - -namespace Tgstation.Server.Host.Components -{ - /// - /// For retrieving current - /// - interface ILaunchParametersFactory - { - /// - /// Get the latest - /// - /// The for the operation - /// A resulting in the latest - Task GetLaunchParameters(CancellationToken cancellationToken); - } -} diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index aea3891519..04554e81ec 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components @@ -20,7 +21,7 @@ namespace Tgstation.Server.Host.Components public IDreamMaker DreamMaker { get; } /// - public IDreamDaemon DreamDaemon { get; } + public IWatchdog Watchdog { get; } /// public IChat Chat { get; } @@ -52,13 +53,13 @@ namespace Tgstation.Server.Host.Components /// CancellationTokenSource timerCts; - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory) + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); Byond = byond ?? throw new ArgumentNullException(nameof(byond)); DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)); - DreamDaemon = dreamDaemon ?? throw new ArgumentNullException(nameof(dreamDaemon)); + watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); @@ -83,19 +84,22 @@ namespace Tgstation.Server.Host.Components await Task.Delay(new TimeSpan(0, minutes, 0), cancellationToken).ConfigureAwait(false); string accessToken = null, projectName = null; + int timeout = 0; var dbTask = databaseContextFactory.UseContext(async (db) => { var instanceQuery = db.Instances.Where(x => x.Id == metadata.Id); + var timeoutTask = instanceQuery.Select(x => x.DreamDaemonSettings.StartupTimeout).FirstAsync(cancellationToken); var projectNameTask = instanceQuery.Select(x => x.DreamMakerSettings.ProjectName).FirstOrDefaultAsync(cancellationToken); accessToken = await instanceQuery.Select(x => x.RepositorySettings.AccessToken).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); projectName = await projectNameTask.ConfigureAwait(false); + timeout = (await timeoutTask.ConfigureAwait(false)).Value; }); using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { await dbTask.ConfigureAwait(false); await repo.FetchOrigin(accessToken, cancellationToken).ConfigureAwait(false); await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); - await DreamMaker.Compile(projectName, repo, cancellationToken).ConfigureAwait(false); + await DreamMaker.Compile(projectName, timeout, repo, cancellationToken).ConfigureAwait(false); } } } @@ -114,10 +118,10 @@ namespace Tgstation.Server.Host.Components } /// - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)); + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)); /// - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); /// public async Task SetAutoUpdateInterval(int? newInterval) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 18e6ef8dda..b7ee4ce2fd 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -1,14 +1,82 @@ -using System; +using Microsoft.Extensions.Hosting; +using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Watchdog { - interface IWatchdog : IDisposable + public interface IWatchdog : IHostedService, IDisposable { - DreamDaemonLaunchParameters LaunchParameters { get; set; } + /// + /// If the watchdog is running + /// + bool Running { get; } + /// + /// If the alpha server is the active server + /// + bool AlphaIsActive { get; } + + /// + /// The latest of the twin servers + /// + LaunchResult LastLaunchResult { get; } + + /// + /// The that is currently live + /// + Models.CompileJob LiveCompileJob { get; } + + /// + /// The that is staged to go live + /// + Models.CompileJob StagedCompileJob { get; } + + /// + /// The the active server is using + /// + DreamDaemonLaunchParameters ActiveLaunchParameters { get; } + + /// + /// The to be applied + /// + DreamDaemonLaunchParameters LastLaunchParameters { get; } + + /// + /// The of the active server + /// + RebootState? RebootState { get; } + + /// + /// Start the + /// + /// The for the operation + /// A resulting in the or if it was already running Task Launch(CancellationToken cancellationToken); + + /// + /// Changes the . If currently triggers a graceful restart + /// + /// The new + /// The for the operation + /// A representing the running operation + Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken); + + /// + /// Restarts the watchdog + /// + /// If the restart will be delayed until a reboot is detected in the active server's DMAPI and this function will retrun immediately + /// The for the operation + /// A resulting in the or if it was already running or is and is + Task Restart(bool graceful, CancellationToken cancellationToken); + + /// + /// Stops the watchdog + /// + /// If the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately + /// The for the operation + /// A representing the running operation + Task Terminate(bool graceful, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs index 8f8ae755ab..016d9f57d0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Represents the result of trying to start a DD process /// - sealed class LaunchResult + public sealed class LaunchResult { /// /// The time it took for to return diff --git a/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs b/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs index b1fc80317a..6890df7bab 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/RebootState.cs @@ -3,7 +3,7 @@ /// /// Represents the action to take when /world/Reboot() is called /// - enum RebootState : int + public enum RebootState : int { /// /// Run DreamDaemon's normal reboot process diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 4152b8bfb4..856aa8a941 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -77,8 +77,5 @@ namespace Tgstation.Server.Host.Components.Watchdog disposed = true; } } - - /// - public Task Launch(CancellationToken cancellationToken) => throw new NotImplementedException(); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs index 3eb27b555a..0532326e78 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogLaunchResult.cs @@ -5,27 +5,16 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Launch results for a /// - sealed class WatchdogLaunchResult + public sealed class WatchdogLaunchResult { - /// - /// If the watchdog is running - /// - bool Running { - get { - if (Alpha == null || Bravo == null) - throw new InvalidOperationException("Alpha or Bravo is null!"); - return !Alpha.ExitCode.HasValue && !Bravo.ExitCode.HasValue; - } - } - /// /// The for the alpha process /// - LaunchResult Alpha { get; set; } + public LaunchResult Alpha { get; set; } /// /// The for the bravo process /// - LaunchResult Bravo { get; set; } + public LaunchResult Bravo { get; set; } } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index a4570e2ff7..356e52fb9d 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -8,9 +8,9 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -51,17 +51,9 @@ namespace Tgstation.Server.Host.Controllers { var instance = instanceManager.GetInstance(Instance); - if (instance.DreamDaemon.Running) + if (instance.Watchdog.Running) return StatusCode(HttpStatusCode.Gone); - var launchParams = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => new DreamDaemonLaunchParameters - { - AllowWebClient = x.DreamDaemonSettings.AllowWebClient, - PrimaryPort = x.DreamDaemonSettings.PrimaryPort, - SecondaryPort = x.DreamDaemonSettings.SecondaryPort, - SecurityLevel = x.DreamDaemonSettings.SecurityLevel - }).FirstAsync(cancellationToken).ConfigureAwait(false); - await jobManager.RegisterOperation(new Models.Job { Description = "Launch DreamDaemon", @@ -69,7 +61,16 @@ namespace Tgstation.Server.Host.Controllers CancelRightsType = RightsType.DreamDaemon, Instance = Instance, StartedBy = AuthenticationContext.User - }, (job, serviceProvider, innerCt) => instance.DreamDaemon.Launch(launchParams, innerCt), cancellationToken).ConfigureAwait(false); + }, + async (job, serviceProvider, innerCt) => + { + var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false); + if (result == null) + throw new InvalidOperationException("Watchdog already running!"); + if (!instance.Watchdog.Running) + throw new Exception("Failed to launch watchdog!"); + }, + cancellationToken).ConfigureAwait(false); return Ok(); } @@ -77,48 +78,55 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)] public override async Task Read(CancellationToken cancellationToken) { - var dd = instanceManager.GetInstance(Instance).DreamDaemon; + var dd = instanceManager.GetInstance(Instance).Watchdog; var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadMetadata) != 0; var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadRevision) != 0; var settings = metadata ? await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false) : null; - Api.Models.DreamDaemon result = new Api.Models.DreamDaemon(); + var result = new DreamDaemon(); if(metadata) { + var alphaActive = dd.AlphaIsActive; + var llp = dd.LastLaunchParameters; result.AutoStart = settings.AutoStart; - result.CurrentPort = dd.CurrentPort; - result.CurrentSecurity = dd.CurrentSecurity; - result.PrimaryPort = dd.LastLaunchParameters.PrimaryPort; - result.AllowWebClient = dd.LastLaunchParameters.AllowWebClient; + result.CurrentPort = alphaActive ? llp.PrimaryPort : llp.SecondaryPort; + result.CurrentSecurity = llp.SecurityLevel; + result.CurrentAllowWebclient = llp.AllowWebClient; + result.PrimaryPort = dd.ActiveLaunchParameters.PrimaryPort; + result.AllowWebClient = dd.ActiveLaunchParameters.AllowWebClient; result.Running = dd.Running; result.SecondaryPort = dd.LastLaunchParameters.SecondaryPort; result.SecurityLevel = dd.LastLaunchParameters.SecurityLevel; - result.SoftRestart = dd.SoftRebooting; - result.SoftShutdown = dd.SoftStopping; + var rstate = dd.RebootState; + result.SoftRestart = rstate == RebootState.Restart; + result.SoftShutdown = rstate == RebootState.Shutdown; }; if (revision) - result.CompileJob = dd.LastCompileJob.ToApi(); + { + result.ActiveCompileJob = dd.LiveCompileJob?.ToApi(); + result.StagedCompileJob = dd.StagedCompileJob?.ToApi(); + } return Json(result); } /// [TgsAuthorize(DreamDaemonRights.Shutdown)] - public override async Task Delete([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + public override async Task Delete([FromBody] DreamDaemon model, CancellationToken cancellationToken) { var instance = instanceManager.GetInstance(Instance); - if (!instance.DreamDaemon.Running) + if (!instance.Watchdog.Running) return StatusCode(HttpStatusCode.Gone); - await instance.DreamDaemon.Terminate(false, cancellationToken).ConfigureAwait(false); + await instance.Watchdog.Terminate(false, cancellationToken).ConfigureAwait(false); return Ok(); } /// [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start)] - public override async Task Update([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + public override async Task Update([FromBody] DreamDaemon model, CancellationToken cancellationToken) { var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); @@ -155,7 +163,7 @@ namespace Tgstation.Server.Host.Controllers current.SoftShutdown = model.SoftShutdown; } - await instanceManager.GetInstance(Instance).DreamDaemon.ChangeSettings(current, cancellationToken).ConfigureAwait(false); + await instanceManager.GetInstance(Instance).Watchdog.ChangeSettings(current, cancellationToken).ConfigureAwait(false); await DatabaseContext.Save(default).ConfigureAwait(false); return Ok(); From e4aca1c8daeceda3a2a5ad23ca43cf23c95a3d06 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Jul 2018 13:07:03 -0400 Subject: [PATCH 176/397] Always dreaming never stopping --- .../Components/Watchdog/Executor.cs | 1 - .../Watchdog/ISessionControllerFactory.cs | 16 ++ .../Components/Watchdog/Watchdog.cs | 230 ++++++++++++++++-- .../Components/Watchdog/WatchdogFactory.cs | 13 +- ...verUpdateConsumer.cs => IServerUpdater.cs} | 12 +- .../Core/SemaphoreContext.cs | 60 +++++ src/Tgstation.Server.Host/Server.cs | 10 +- .../Core/TestApplication.cs | 2 +- 8 files changed, 315 insertions(+), 29 deletions(-) rename src/Tgstation.Server.Host/Core/{IServerUpdateConsumer.cs => IServerUpdater.cs} (60%) create mode 100644 src/Tgstation.Server.Host/Core/SemaphoreContext.cs diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs index e31b223d2a..c422c33730 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs @@ -45,7 +45,6 @@ namespace Tgstation.Server.Host.Components.Watchdog throw new ArgumentNullException(nameof(parameters)); var proc = new Process(); - try { proc.StartInfo.FileName = byondLock.DreamDaemonPath; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs index 49bcbcc383..32faa37b0e 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs @@ -9,8 +9,24 @@ namespace Tgstation.Server.Host.Components.Watchdog /// interface ISessionControllerFactory { + /// + /// Create a from a freshly launch DreamDaemon instance + /// + /// The to use + /// The to use + /// If the of should be used + /// If the of should be used + /// If the should only validate the DMAPI then exit + /// The for the operation + /// A resulting in a new Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken); + /// + /// Create a from an existing DreamDaemon instance + /// + /// The to use + /// The for the operation + /// A resulting in a new Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 856aa8a941..1f390752eb 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -1,7 +1,10 @@ using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Watchdog { @@ -9,11 +12,28 @@ namespace Tgstation.Server.Host.Components.Watchdog sealed class Watchdog : IWatchdog { /// - public DreamDaemonLaunchParameters LaunchParameters - { - get => launchParameters; - set => launchParameters = value ?? throw new ArgumentNullException(nameof(value)); - } + public bool Running { get; private set; } + + /// + public bool AlphaIsActive { get; private set; } + + /// + public LaunchResult LastLaunchResult { get; private set; } + + /// + public Models.CompileJob LiveCompileJob { get; private set; } + + /// + public Models.CompileJob StagedCompileJob { get; private set; } + + /// + public DreamDaemonLaunchParameters ActiveLaunchParameters { get; private set; } + + /// + public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; } + + /// + public RebootState? RebootState => Running ? (RebootState?)(AlphaIsActive ? alphaServer.RebootState : bravoServer.RebootState) : null; /// /// The for the @@ -23,7 +43,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The for the /// - readonly ISessionControllerFactory sessionManagerFactory; + readonly ISessionControllerFactory sessionControllerFactory; /// /// The for the @@ -40,42 +60,216 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IDmbFactory dmbFactory; - DreamDaemonLaunchParameters launchParameters; + + /// + /// The for the + /// + readonly SemaphoreSlim semaphore; ISessionController alphaServer; ISessionController bravoServer; - bool alphaActive; - - bool disposed; + /// + /// If the servers should be released instead of shutdown + /// + bool releaseServers; /// /// Construct a /// /// The value of - /// The value of + /// The value of /// The value of /// The value of /// The value of - public Watchdog(IByond byond, IChat chat, ISessionControllerFactory sessionManagerFactory, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar, DreamDaemonLaunchParameters initialLaunchParameters) + /// The for the + /// The initial value of + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar, IServerUpdater serverUpdater, DreamDaemonLaunchParameters initialLaunchParameters) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); - this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); + this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.interopRegistrar = interopRegistrar ?? throw new ArgumentNullException(nameof(interopRegistrar)); - LaunchParameters = initialLaunchParameters; + + if(serverUpdater == null) + throw new ArgumentNullException(nameof(serverUpdater)); + + serverUpdater.RegisterForUpdate(() => releaseServers = true); + + AlphaIsActive = true; + ActiveLaunchParameters = initialLaunchParameters; + releaseServers = false; + semaphore = new SemaphoreSlim(1); } /// public void Dispose() { - lock (this) + DisposeAndNullControllers(); + semaphore.Dispose(); + } + + + void DisposeAndNullControllers() + { + alphaServer?.Dispose(); + alphaServer = null; + bravoServer?.Dispose(); + bravoServer = null; + } + + /// + public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) + { + using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { - alphaServer?.Dispose(); - bravoServer?.Dispose(); - disposed = true; + ActiveLaunchParameters = launchParameters; + if (Running) + await RestartNoLock(true, cancellationToken).ConfigureAwait(false); + + //WHEN YOU GET BACK + //change graceful restart to immediately restart the inactive server and then relaunch it + //this func is done, don't worry } } + + /// + public async Task Launch(CancellationToken cancellationToken) + { + using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + using(var alphaStartCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { + if (Running) + return null; + //start both servers + LastLaunchParameters = ActiveLaunchParameters; + var dmbToUse = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false); + Task alphaServerTask = null; + + try + { + try + { + alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, true, true, false, alphaStartCts.Token); + //do a few seconds of delay so that any backends the servers use know that alpha came first + await Task.Delay(5000, cancellationToken).ConfigureAwait(false); + var bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, false, false, false, cancellationToken); + bravoServer = await bravoServerTask.ConfigureAwait(false); + alphaServer = await alphaServerTask.ConfigureAwait(false); + } + catch + { + if (alphaServerTask != null) + if (alphaServerTask.Status == TaskStatus.RanToCompletion) + alphaServer = await alphaServerTask.ConfigureAwait(false); + else + { + alphaStartCts.Cancel(); + try + { + alphaServer = await alphaServerTask.ConfigureAwait(false); + } + catch { } + } + throw; + } + + async Task CheckLaunch(ISessionController controller, string serverName) + { + var launch = await controller.LaunchResult.ConfigureAwait(false); + if (launch.ExitCode.HasValue) + //you killed us ray... + throw new Exception(String.Format(CultureInfo.InvariantCulture, "{2} server failed to start: Exit Code: {0}, RAM: {1}, Runtime {3}ms", launch.ExitCode, launch.StartupTime, serverName, launch.StartupTime.TotalMilliseconds)); + return launch; + } + + var alphaLrt = CheckLaunch(alphaServer, "Alpha"); + var bravoLrt = CheckLaunch(bravoServer, "Bravo"); + //now we have two booting servers, get them up and running + var allTask = Task.WhenAll(alphaLrt, bravoLrt); + + //don't forget about the cancelationToken + var cancelTcs = new TaskCompletionSource(); + using (cancellationToken.Register(() => cancelTcs.SetCanceled())) + await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false); + + //both servers are now running, alpha is the active server, huzzah + LiveCompileJob = dmbToUse.CompileJob; + LastLaunchResult = alphaLrt.Result; + StagedCompileJob = null; + AlphaIsActive = true; + Running = true; + + return new WatchdogLaunchResult + { + Alpha = alphaLrt.Result, + Bravo = bravoLrt.Result + }; + } + catch + { + DisposeAndNullControllers(); + throw; + } + } + } + + async Task RestartNoLock(bool graceful, CancellationToken cancellationToken) + { + var running = Running; + if (!graceful || !running) + { + if (running) + await Terminate(false, cancellationToken).ConfigureAwait(false); + return await Launch(cancellationToken).ConfigureAwait(false); + } + var toReboot = AlphaIsActive ? alphaServer : bravoServer; + if (toReboot != null) + await toReboot.SetRebootState(Components.Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false); + return null; + } + + /// + public async Task Restart(bool graceful, CancellationToken cancellationToken) + { + using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + return await RestartNoLock(graceful, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task Terminate(bool graceful, CancellationToken cancellationToken) + { + using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + if (!Running) + return; + if (!graceful) + { + DisposeAndNullControllers(); + return; + } + var toKill = AlphaIsActive ? alphaServer : bravoServer; + if (toKill != null) + await toKill.SetRebootState(Components.Watchdog.RebootState.Shutdown, cancellationToken).ConfigureAwait(false); + } + } + + /// + public Task StartAsync(CancellationToken cancellationToken) => Launch(cancellationToken); + + /// + public Task StopAsync(CancellationToken cancellationToken) + { + if (releaseServers) + { + ReattachInformation reattachInformation; + if (AlphaIsActive) + reattachInformation = alphaServer?.Release(); + else + reattachInformation = bravoServer?.Release(); + } + return Terminate(false, cancellationToken); + } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index e11ce4cd29..dc93621c80 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -1,5 +1,6 @@ using System; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog { @@ -31,6 +32,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IInteropRegistrar interopRegistrar; + /// + /// The for the + /// + readonly IServerUpdater serverUpdater; + /// /// Construct a /// @@ -39,16 +45,17 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public WatchdogFactory(IByond byond, IChat chat, ISessionControllerFactory sessionManagerFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar) + /// The value of + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionManagerFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar, IServerUpdater serverUpdater) { - this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.interopRegistrar = interopRegistrar ?? throw new ArgumentNullException(nameof(interopRegistrar)); + this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters) => new Watchdog(byond, chat, sessionManagerFactory, dmbFactory, eventConsumer, interopRegistrar, launchParameters); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters) => new Watchdog(chat, sessionManagerFactory, dmbFactory, eventConsumer, interopRegistrar, serverUpdater, launchParameters); } } diff --git a/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs b/src/Tgstation.Server.Host/Core/IServerUpdater.cs similarity index 60% rename from src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs rename to src/Tgstation.Server.Host/Core/IServerUpdater.cs index 9055b7206e..c4b43eb1b2 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdateConsumer.cs +++ b/src/Tgstation.Server.Host/Core/IServerUpdater.cs @@ -1,14 +1,22 @@ -namespace Tgstation.Server.Host.Core +using System; + +namespace Tgstation.Server.Host.Core { /// /// Represents a service that may take an updated assembly and run it, stopping the current assembly in the process /// - interface IServerUpdateConsumer + interface IServerUpdater { /// /// Run a new assembly and stop the current one. This will likely trigger all active s /// /// The path to the new assembly void ApplyUpdate(string updatePath); + + /// + /// Register a given to run before stopping the server for updates + /// + /// The to run + void RegisterForUpdate(Action action); } } diff --git a/src/Tgstation.Server.Host/Core/SemaphoreContext.cs b/src/Tgstation.Server.Host/Core/SemaphoreContext.cs new file mode 100644 index 0000000000..f2665250d8 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/SemaphoreContext.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Async lock context helper + /// + public sealed class SemaphoreContext : IDisposable + { + /// + /// Asyncronously locks a + /// + /// The to lock + /// The for the operation + /// A resulting in the for the lock + public static async Task Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken) + { + if (semaphore == null) + throw new ArgumentNullException(nameof(semaphore)); + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + return new SemaphoreContext(semaphore); + } + + /// + /// The locked + /// + readonly SemaphoreSlim lockedSemaphore; + + /// + /// If has been called + /// + bool disposed; + + /// + /// Construct a + /// + /// The value of + SemaphoreContext(SemaphoreSlim lockedSemaphore) => this.lockedSemaphore = lockedSemaphore; + + /// + /// Finalize the + /// + ~SemaphoreContext() => Dispose(); + + /// + /// Release the lock on + /// + public void Dispose() + { + if (disposed) + return; + lockedSemaphore.Release(); + GC.SuppressFinalize(this); + disposed = true; + } + } +} diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 506424c49c..ddf5ec2ece 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -10,7 +10,7 @@ using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host { /// - sealed class Server : IServer, IServerUpdateConsumer + sealed class Server : IServer, IServerUpdater { /// public string UpdatePath { get; private set; } @@ -35,11 +35,10 @@ namespace Tgstation.Server.Host [ExcludeFromCodeCoverage] public async Task RunAsync(CancellationToken cancellationToken) { - cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - using (cancellationTokenSource) + using (cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) using (var webHost = webHostBuilder .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) await webHost.RunAsync(cancellationToken).ConfigureAwait(false); @@ -56,5 +55,8 @@ namespace Tgstation.Server.Host } cancellationTokenSource.Cancel(); } + + /// + public void RegisterForUpdate(Action action) => cancellationTokenSource.Token.Register(action); } } diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 47ac493ecc..afd32b4923 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Core.Tests { [TestClass] - public sealed class TestApplication : IServerUpdateConsumer + public sealed class TestApplication : IServerUpdater { public void ApplyUpdate(string updatePath) => throw new System.NotImplementedException(); From 02bc572b6b512e7b0d29fae7584248799d13437d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Jul 2018 16:30:51 -0400 Subject: [PATCH 177/397] To shreads you say? --- src/DMAPI/tgs/v4/api.dm | 10 +- src/Tgstation.Server.Host/Components/IChat.cs | 8 + .../Components/Watchdog/ISessionController.cs | 10 + .../Components/Watchdog/SessionController.cs | 36 ++- .../Components/Watchdog/Watchdog.cs | 276 ++++++++++++++++-- 5 files changed, 302 insertions(+), 38 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 8622f49255..01e46eb152 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -9,6 +9,7 @@ #define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm" #define TGS4_TOPIC_EVENT "tgs_event" +#define TGS4_COMM_ONLINE "tgs_on" #define TGS4_COMM_IDENTIFY "tgs_ident" #define TGS4_COMM_VALIDATE "tgs_vali" #define TGS4_COMM_SERVER_PRIMED "tgs_prime" @@ -93,12 +94,6 @@ return "No command!" switch(command) - if(TGS4_TOPIC_SWAP) - SwapPorts(FALSE) - return TGS4_TOPIC_SUCCESS - if(TGS4_TOPIC_SWAP_DELAYED) - SwapPorts(TRUE) - return TGS4_TOPIC_SUCCESS if(TGS4_TOPIC_CHAT_COMMAND) var/result = HandleCustomCommand(params[TGS4_TOPIC_CHAT_COMMAND]) if(!result) @@ -110,9 +105,6 @@ . = json_encode(intercepted_message_queue) intercepted_message_queue = null return - if(TGS4_TOPIC_IDENTIFY) - //they want to know our initial port - return "[port_1]" return "Unknown command: [command]" diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/IChat.cs index 1035bdbbda..843abf3164 100644 --- a/src/Tgstation.Server.Host/Components/IChat.cs +++ b/src/Tgstation.Server.Host/Components/IChat.cs @@ -47,6 +47,14 @@ namespace Tgstation.Server.Host.Components /// A representing the running operation Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken); + /// + /// Send a chat to configured watchdog channels + /// + /// The message being sent + /// The for the operation + /// A representing the running operation + Task SendWatchdogMessage(string message, CancellationToken cancellationToken); + /// /// Start tracking json files for commands and channels /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs index af91fe2d32..efae086297 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs @@ -33,6 +33,16 @@ namespace Tgstation.Server.Host.Components.Watchdog /// RebootState RebootState { get; } + /// + /// If the port should close when /world/Reboot() is called. Defaults to + /// + bool ClosePortOnReboot { get; set; } + + /// + /// A that completes when the server calls /world/Reboot() + /// + Task OnReboot { get; } + /// /// Releases the without terminating it. Also calls /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 90a8782bd3..28bd36126b 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -28,6 +28,7 @@ namespace Tgstation.Server.Host.Components.Watchdog const string DMTopicChatCommand = "tgs_chat_comm"; const string DMTopicEvent = "tgs_event"; + const string DMCommandOnline = "tgs_on"; const string DMCommandIdentify = "tgs_ident"; const string DMCommandApiValidate = "tgs_validate"; const string DMCommandServerPrimed = "tgs_prime"; @@ -41,7 +42,6 @@ namespace Tgstation.Server.Host.Components.Watchdog const string DMParameterNewPort = "new_port"; const string DMParameterNewRebootMode = "new_rmode"; - /// public bool IsPrimary { @@ -52,6 +52,9 @@ namespace Tgstation.Server.Host.Components.Watchdog } } + /// + public bool ClosePortsOnReboot { get; set; } + /// public bool ApiValidated { @@ -138,7 +141,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The port to assign DreamDaemon when it queries for it /// - ushort? nextPort; + ushort nextPort; /// /// If we know DreamDaemon currently has it's port closed @@ -209,18 +212,31 @@ namespace Tgstation.Server.Host.Components.Watchdog { case DMCommandIdentify: lock (this) - if (nextPort.HasValue) + if (portClosed) + content = new Dictionary { { DMParameterNewPort, nextPort } }; + break; + case DMCommandOnline: + lock (this) + if (portClosed) { - var newPort = nextPort.Value; - nextPort = null; + reattachInformation.Port = nextPort; portAssignmentTcs.SetResult(true); portAssignmentTcs = null; - content = new Dictionary { { DMParameterNewPort, newPort } }; + portClosed = false; } break; case DMCommandApiValidate: apiValidated = true; break; + case DMCommandWorldReboot: + if (ClosePortsOnReboot) + { + content = new Dictionary { { DMParameterNewPort, 0 } }; + portClosed = true; + } + else + ClosePortsOnReboot = true; + break; default: status = HttpStatusCode.BadRequest; content = new { message = "Requested command not supported!" }; @@ -276,14 +292,18 @@ namespace Tgstation.Server.Host.Components.Watchdog CheckDisposed(); if (portClosed) { - Task toWait; + Task toWait; lock (this) { + if (portAssignmentTcs != null) + //someone was trying to change the port before us, ignore them + //shouldn't happen anyway, add logging here + portAssignmentTcs.SetResult(false); nextPort = port; portAssignmentTcs = new TaskCompletionSource(); toWait = portAssignmentTcs.Task; } - await toWait.ConfigureAwait(false); + return await toWait.ConfigureAwait(false); } if (port == 0) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 1f390752eb..1b2de41de1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -60,12 +60,14 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IDmbFactory dmbFactory; - /// /// The for the /// readonly SemaphoreSlim semaphore; + CancellationTokenSource monitorCts; + Task monitorTask; + ISessionController alphaServer; ISessionController bravoServer; @@ -119,6 +121,233 @@ namespace Tgstation.Server.Host.Components.Watchdog bravoServer = null; } + async Task RestartNoLock(bool graceful, CancellationToken cancellationToken) + { + var running = Running; + if (!graceful || !running) + { + if (running) + await Terminate(false, cancellationToken).ConfigureAwait(false); + return await Launch(cancellationToken).ConfigureAwait(false); + } + var toReboot = AlphaIsActive ? alphaServer : bravoServer; + var other = AlphaIsActive ? bravoServer : alphaServer; + if (toReboot != null) + //todo, log the result + await toReboot.SetRebootState(Components.Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false); + return null; + } + + async Task MonitorLifetimes(CancellationToken cancellationToken) + { + var watchReboot = false; + while(true) + { + try + { + var alphaServerTask = alphaServer.Lifetime; + var bravoServerTask = bravoServer.Lifetime; + var alphaServerReboot = alphaServer.OnReboot; + var bravoServerReboot = bravoServer.OnReboot; + + var activeServer = AlphaIsActive ? alphaServer : bravoServer; + var inactiveServer = AlphaIsActive ? bravoServer : alphaServer; + + var cancelTcs = new TaskCompletionSource(); + using (cancellationToken.Register(() => cancelTcs.SetCanceled())) + { + var toWaitOn = Task.WhenAny(alphaServerTask, bravoServerTask, alphaServerReboot, bravoServerReboot, cancelTcs.Task); + if (!watchReboot) + toWaitOn = Task.WhenAny(toWaitOn, inactiveServer.LaunchResult); + await toWaitOn.ConfigureAwait(false); + } + + using (await SemaphoreContext.Lock(semaphore, default).ConfigureAwait(false)) + { + + var activeServerExited = (alphaServerTask.IsCompleted && AlphaIsActive) || (bravoServerTask.IsCompleted && !AlphaIsActive); + var inactiveServerExited = (alphaServerTask.IsCompleted && !AlphaIsActive) || (bravoServerTask.IsCompleted && AlphaIsActive); + var activeServerReboot = (alphaServerReboot.IsCompleted && AlphaIsActive) || (bravoServerReboot.IsCompleted && !AlphaIsActive); + + if (watchReboot) + { + //waiting on the inactive server to come back + var doContinue = true; + var activeServerGracefulAction = activeServer.RebootState != Components.Watchdog.RebootState.Normal; + var aServerRebooted = alphaServerReboot.IsCompleted || bravoServerReboot.IsCompleted; + if (inactiveServer.LaunchResult.IsCompleted) + { + //glad to have you back + //(cleanup the task here) + LastLaunchResult = await inactiveServer.LaunchResult.ConfigureAwait(false); + if (inactiveServer.Lifetime.IsCompleted) + { + //aww heck you died again, try to bring you back i guess... + //transfer control to the "inactive server died" branch + inactiveServerExited = true; + doContinue = false; + } + else + //reenable port swapping on active server + activeServer.ClosePortOnReboot = true; + } + else if (activeServerGracefulAction && activeServerExited) + { + //ok not so bad, we either kill everything or restart everything + if (activeServer.RebootState == Components.Watchdog.RebootState.Shutdown) + //ez + //let control fall through to the "crash but wanted to shutdown anyway" section + doContinue = false; + else + { + //total restart, forget about trying to make the inactive server the active one since it's not even responding yet + var chatTask = chat.SendWatchdogMessage("Entered state where restart is requested but neither server is ready. Restarting watchdog...", cancellationToken); + await LaunchNoLock(false, cancellationToken).ConfigureAwait(false); + await chatTask.ConfigureAwait(false); + if (!Running) + return; + } + } + //if the above is NOT the case that means something probably happened to the ACTIVE server + //hopefully it was just a reboot, otherwise we're gonna need to do some weird recovery shennanigans + else if (!aServerRebooted) + { + //uh oh... + if (!activeServerExited) + //inactive server died again + //let control fall through to the inactive crash handler + doContinue = false; + else + { + //OH NO + //so now we have a dead active server and an inactive server who is still booting + //best we can do is hope it comes online so we can fix things + var chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server crashed or exited (Code: {0})! Inactive server is still booting! Waiting...", activeServer.Lifetime.Result), cancellationToken); + + LastLaunchResult = await inactiveServer.LaunchResult.ConfigureAwait(false); + + if(inactiveServer.Lifetime.IsCompleted) + //he's dead too jim. + } + } + else + { + //thank god + //doesn't matter which server rebooted, our action is still the same: + //if it was active, tell it to not switch port + //if it was inactive, ensure it's port is the right one + activeServer.ClosePortOnReboot = false; + await inactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false); + } + + if (doContinue) + continue; + } + + if (activeServerExited) + { + //active server just went down + switch (activeServer.RebootState) + { + case Components.Watchdog.RebootState.Restart: + case Components.Watchdog.RebootState.Normal: + //yeah he died unexpectedly + //bring in other + var chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server crashed or exited (Code: {0})! Activating backup, relaunching in background...", activeServer.Lifetime.Result), cancellationToken); + + activeServer.Dispose(); + var backupOnline = await inactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); + if (!backupOnline) + { + //can't bring in backup, reboot everything + await chatTask.ConfigureAwait(false); + await chat.SendWatchdogMessage("Backup server not responding to port switch request! Restarting watchdog...", cancellationToken).ConfigureAwait(false); + inactiveServer.Dispose(); + Running = false; + await LaunchNoLock(false, cancellationToken).ConfigureAwait(false); + if (!Running) + return; + continue; + } + + //make a new server with blackjack and hookers + var nextDmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false); + var newServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, nextDmb, false, AlphaIsActive, false, cancellationToken).ConfigureAwait(false); + + if (AlphaIsActive) + alphaServer = newServer; + else + bravoServer = newServer; + watchReboot = true; + AlphaIsActive = !AlphaIsActive; + inactiveServer.ClosePortOnReboot = false; //actually the active server at this point + break; + case Components.Watchdog.RebootState.Shutdown: + await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server crashed or exited (Code: {0})! Not recovering to to pending shutdown.", activeServer.Lifetime.Result), cancellationToken).ConfigureAwait(false); + DisposeAndNullControllers(); + return; + } + } + else if (inactiveServerExited) + { + //inactive server just went down + //bring it back + var chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Inactive server crashed or exited (Code: {0})! Relaunching in background...", inactiveServer.Lifetime.Result), cancellationToken); + var nextDmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false); + var newServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, nextDmb, false, AlphaIsActive, false, cancellationToken).ConfigureAwait(false); + + if (!AlphaIsActive) + alphaServer = newServer; + else + bravoServer = newServer; + + watchReboot = true; + activeServer.ClosePortOnReboot = false; + } + else if (activeServerReboot) + { + //try 5 times to open the inactive server on the primary port + //might fail a couple times due to the active server in the process of closing it's own + var I = 0; + for (; I < 5; ++I) + if (await inactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false)) + break; + + if(I == 5) + { + //uggghhh he's being a bitch + var chatTask = chat.SendWatchdogMessage("Unable to open inactive server port! Reopening active server port...", cancellationToken); + var result = await activeServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); + if (!result) + { + //ok reboot time + await chatTask.ConfigureAwait(false); + await chat.SendWatchdogMessage("Unable to reopen active server port! Restarting watchdog...", cancellationToken).ConfigureAwait(false); + inactiveServer.Dispose(); + Running = false; + await LaunchNoLock(false, cancellationToken).ConfigureAwait(false); + if (!Running) + return; + } + continue; + } + + LastLaunchParameters = ActiveLaunchParameters; + AlphaIsActive = !AlphaIsActive; + LiveCompileJob = inactiveServer.Dmb.CompileJob; + StagedCompileJob = null; + this. + } + } + } + catch (OperationCanceledException) { } + catch (Exception e) + { + await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Monitor crashed, this should NEVER happen! Restarting monitor... Error: {0}", e.Message), cancellationToken).ConfigureAwait(false); + } + } + } + /// public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { @@ -127,18 +356,22 @@ namespace Tgstation.Server.Host.Components.Watchdog ActiveLaunchParameters = launchParameters; if (Running) await RestartNoLock(true, cancellationToken).ConfigureAwait(false); - - //WHEN YOU GET BACK - //change graceful restart to immediately restart the inactive server and then relaunch it - //this func is done, don't worry } } - /// - public async Task Launch(CancellationToken cancellationToken) + async Task StopMonitor() { - using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) - using(var alphaStartCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + if (monitorTask == null) + return; + monitorCts.Cancel(); + await monitorTask.ConfigureAwait(false); + monitorCts.Dispose(); + monitorTask = null; + } + + async Task LaunchNoLock(bool startMonitor, CancellationToken cancellationToken) + { + using (var alphaStartCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { if (Running) return null; @@ -201,6 +434,13 @@ namespace Tgstation.Server.Host.Components.Watchdog AlphaIsActive = true; Running = true; + if (startMonitor) + { + await StopMonitor().ConfigureAwait(false); + monitorCts = new CancellationTokenSource(); + monitorTask = MonitorLifetimes(monitorCts.Token); + } + return new WatchdogLaunchResult { Alpha = alphaLrt.Result, @@ -215,19 +455,11 @@ namespace Tgstation.Server.Host.Components.Watchdog } } - async Task RestartNoLock(bool graceful, CancellationToken cancellationToken) + /// + public async Task Launch(CancellationToken cancellationToken) { - var running = Running; - if (!graceful || !running) - { - if (running) - await Terminate(false, cancellationToken).ConfigureAwait(false); - return await Launch(cancellationToken).ConfigureAwait(false); - } - var toReboot = AlphaIsActive ? alphaServer : bravoServer; - if (toReboot != null) - await toReboot.SetRebootState(Components.Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false); - return null; + using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + return await LaunchNoLock(true, cancellationToken).ConfigureAwait(false); } /// @@ -244,12 +476,14 @@ namespace Tgstation.Server.Host.Components.Watchdog { if (!Running) return; + await StopMonitor().ConfigureAwait(false); if (!graceful) { DisposeAndNullControllers(); return; } var toKill = AlphaIsActive ? alphaServer : bravoServer; + var other = AlphaIsActive ? bravoServer : alphaServer; if (toKill != null) await toKill.SetRebootState(Components.Watchdog.RebootState.Shutdown, cancellationToken).ConfigureAwait(false); } From 8d84ca13a608289730378ea9a771565bdd0ff243 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 5 Jul 2018 16:52:47 -0400 Subject: [PATCH 178/397] Can't wait forever --- .../Watchdog/MonitorActivationReason.cs | 12 ++ .../Components/Watchdog/MonitorState.cs | 11 ++ .../Components/Watchdog/Watchdog.cs | 187 ++---------------- 3 files changed, 35 insertions(+), 175 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs new file mode 100644 index 0000000000..1c36c6f0d6 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs @@ -0,0 +1,12 @@ +namespace Tgstation.Server.Host.Components.Watchdog +{ + enum MonitorActivationReason + { + ActiveServerCrashed, + InactiveServerCrashed, + ActiveServerRebooted, + InactiveServerRebooted, + NewDmbAvailable, + InactiveServerStartupComplete + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs new file mode 100644 index 0000000000..dfb418f9f4 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs @@ -0,0 +1,11 @@ +namespace Tgstation.Server.Host.Components.Watchdog +{ + sealed class MonitorState + { + public bool RebootingInactiveServer { get; set; } + public bool InactiveServerHasStagedDmb { get; set; } + + public ISessionController ActiveServer { get; set; } + public ISessionController InactiveServer { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 1b2de41de1..5bf055c988 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -138,9 +138,14 @@ namespace Tgstation.Server.Host.Components.Watchdog return null; } + async Task HandlerMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState) + { + + } + async Task MonitorLifetimes(CancellationToken cancellationToken) { - var watchReboot = false; + var state = new MonitorState(); while(true) { try @@ -164,180 +169,12 @@ namespace Tgstation.Server.Host.Components.Watchdog using (await SemaphoreContext.Lock(semaphore, default).ConfigureAwait(false)) { - - var activeServerExited = (alphaServerTask.IsCompleted && AlphaIsActive) || (bravoServerTask.IsCompleted && !AlphaIsActive); - var inactiveServerExited = (alphaServerTask.IsCompleted && !AlphaIsActive) || (bravoServerTask.IsCompleted && AlphaIsActive); - var activeServerReboot = (alphaServerReboot.IsCompleted && AlphaIsActive) || (bravoServerReboot.IsCompleted && !AlphaIsActive); - - if (watchReboot) - { - //waiting on the inactive server to come back - var doContinue = true; - var activeServerGracefulAction = activeServer.RebootState != Components.Watchdog.RebootState.Normal; - var aServerRebooted = alphaServerReboot.IsCompleted || bravoServerReboot.IsCompleted; - if (inactiveServer.LaunchResult.IsCompleted) - { - //glad to have you back - //(cleanup the task here) - LastLaunchResult = await inactiveServer.LaunchResult.ConfigureAwait(false); - if (inactiveServer.Lifetime.IsCompleted) - { - //aww heck you died again, try to bring you back i guess... - //transfer control to the "inactive server died" branch - inactiveServerExited = true; - doContinue = false; - } - else - //reenable port swapping on active server - activeServer.ClosePortOnReboot = true; - } - else if (activeServerGracefulAction && activeServerExited) - { - //ok not so bad, we either kill everything or restart everything - if (activeServer.RebootState == Components.Watchdog.RebootState.Shutdown) - //ez - //let control fall through to the "crash but wanted to shutdown anyway" section - doContinue = false; - else - { - //total restart, forget about trying to make the inactive server the active one since it's not even responding yet - var chatTask = chat.SendWatchdogMessage("Entered state where restart is requested but neither server is ready. Restarting watchdog...", cancellationToken); - await LaunchNoLock(false, cancellationToken).ConfigureAwait(false); - await chatTask.ConfigureAwait(false); - if (!Running) - return; - } - } - //if the above is NOT the case that means something probably happened to the ACTIVE server - //hopefully it was just a reboot, otherwise we're gonna need to do some weird recovery shennanigans - else if (!aServerRebooted) - { - //uh oh... - if (!activeServerExited) - //inactive server died again - //let control fall through to the inactive crash handler - doContinue = false; - else - { - //OH NO - //so now we have a dead active server and an inactive server who is still booting - //best we can do is hope it comes online so we can fix things - var chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server crashed or exited (Code: {0})! Inactive server is still booting! Waiting...", activeServer.Lifetime.Result), cancellationToken); - - LastLaunchResult = await inactiveServer.LaunchResult.ConfigureAwait(false); - - if(inactiveServer.Lifetime.IsCompleted) - //he's dead too jim. - } - } - else - { - //thank god - //doesn't matter which server rebooted, our action is still the same: - //if it was active, tell it to not switch port - //if it was inactive, ensure it's port is the right one - activeServer.ClosePortOnReboot = false; - await inactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false); - } - - if (doContinue) - continue; - } - - if (activeServerExited) - { - //active server just went down - switch (activeServer.RebootState) - { - case Components.Watchdog.RebootState.Restart: - case Components.Watchdog.RebootState.Normal: - //yeah he died unexpectedly - //bring in other - var chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server crashed or exited (Code: {0})! Activating backup, relaunching in background...", activeServer.Lifetime.Result), cancellationToken); - - activeServer.Dispose(); - var backupOnline = await inactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); - if (!backupOnline) - { - //can't bring in backup, reboot everything - await chatTask.ConfigureAwait(false); - await chat.SendWatchdogMessage("Backup server not responding to port switch request! Restarting watchdog...", cancellationToken).ConfigureAwait(false); - inactiveServer.Dispose(); - Running = false; - await LaunchNoLock(false, cancellationToken).ConfigureAwait(false); - if (!Running) - return; - continue; - } - - //make a new server with blackjack and hookers - var nextDmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false); - var newServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, nextDmb, false, AlphaIsActive, false, cancellationToken).ConfigureAwait(false); - - if (AlphaIsActive) - alphaServer = newServer; - else - bravoServer = newServer; - watchReboot = true; - AlphaIsActive = !AlphaIsActive; - inactiveServer.ClosePortOnReboot = false; //actually the active server at this point - break; - case Components.Watchdog.RebootState.Shutdown: - await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server crashed or exited (Code: {0})! Not recovering to to pending shutdown.", activeServer.Lifetime.Result), cancellationToken).ConfigureAwait(false); - DisposeAndNullControllers(); - return; - } - } - else if (inactiveServerExited) - { - //inactive server just went down - //bring it back - var chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Inactive server crashed or exited (Code: {0})! Relaunching in background...", inactiveServer.Lifetime.Result), cancellationToken); - var nextDmb = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false); - var newServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, nextDmb, false, AlphaIsActive, false, cancellationToken).ConfigureAwait(false); - - if (!AlphaIsActive) - alphaServer = newServer; - else - bravoServer = newServer; - - watchReboot = true; - activeServer.ClosePortOnReboot = false; - } - else if (activeServerReboot) - { - //try 5 times to open the inactive server on the primary port - //might fail a couple times due to the active server in the process of closing it's own - var I = 0; - for (; I < 5; ++I) - if (await inactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false)) - break; - - if(I == 5) - { - //uggghhh he's being a bitch - var chatTask = chat.SendWatchdogMessage("Unable to open inactive server port! Reopening active server port...", cancellationToken); - var result = await activeServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); - if (!result) - { - //ok reboot time - await chatTask.ConfigureAwait(false); - await chat.SendWatchdogMessage("Unable to reopen active server port! Restarting watchdog...", cancellationToken).ConfigureAwait(false); - inactiveServer.Dispose(); - Running = false; - await LaunchNoLock(false, cancellationToken).ConfigureAwait(false); - if (!Running) - return; - } - continue; - } - - LastLaunchParameters = ActiveLaunchParameters; - AlphaIsActive = !AlphaIsActive; - LiveCompileJob = inactiveServer.Dmb.CompileJob; - StagedCompileJob = null; - this. - } + state.ActiveServer = AlphaIsActive ? alphaServer : bravoServer; + state.InactiveServer = !AlphaIsActive ? alphaServer : bravoServer; + MonitorActivationReason activationReason; + if (activeServer.Lifetime.IsCompleted) + activationReason = MonitorActivationReason.ActiveServerCrashed; + else if(inactiveServer.Lifetime.IsCompleted) } } catch (OperationCanceledException) { } From 5f80adfc21fe35203700039ec9e6443bf3bfbf68 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Jul 2018 12:16:02 -0400 Subject: [PATCH 179/397] Hey it actually compiles now --- .../Components/DmbFactory.cs | 17 +- .../Components/DreamMaker.cs | 13 +- .../Components/IDmbFactory.cs | 14 +- .../Components/InstanceManager.cs | 1 + .../Watchdog/IReattachInfoHandler.cs | 26 ++ .../Components/Watchdog/LaunchResult.cs | 4 + .../Components/Watchdog/MonitorAction.cs | 13 + .../Components/Watchdog/MonitorState.cs | 2 + .../Components/Watchdog/SessionController.cs | 19 +- .../Watchdog/SessionControllerFactory.cs | 62 ++- .../Components/Watchdog/Watchdog.cs | 429 ++++++++++++------ .../Components/Watchdog/WatchdogFactory.cs | 40 +- .../Watchdog/WatchdogReattachInformation.cs | 6 +- .../Core/TestApplication.cs | 5 +- 14 files changed, 459 insertions(+), 192 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/IReattachInfoHandler.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index 9d21f4747f..e16506e3ab 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -13,6 +13,16 @@ namespace Tgstation.Server.Host.Components /// sealed class DmbFactory : IDmbFactory, ICompileJobConsumer { + /// + public Task OnNewerDmb + { + get + { + lock (this) + return newerDmbTcs.Task; + } + } + /// /// The for the /// @@ -109,13 +119,6 @@ namespace Tgstation.Server.Host.Components return result; } - /// - public Task OnNewerDmb() - { - lock (this) - return newerDmbTcs.Task; - } - /// public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 173332c7d7..2dafc3b983 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -36,6 +36,10 @@ namespace Tgstation.Server.Host.Components /// public CompilerStatus Status { get; private set; } + /// + /// The for + /// + readonly IByond byond; /// /// The for /// @@ -49,10 +53,6 @@ namespace Tgstation.Server.Host.Components /// readonly ISessionControllerFactory sessionControllerFactory; /// - /// The for - /// - readonly IByond byond; - /// /// The for /// readonly ICompileJobConsumer compileJobConsumer; @@ -64,16 +64,17 @@ namespace Tgstation.Server.Host.Components /// /// Construct /// + /// The value of /// The value of /// The value of /// The value of - /// The value of /// The value of /// The value of /// The value of /// - public DreamMaker(IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application) + public DreamMaker(IByond byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application) { + this.byond = byond; this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/IDmbFactory.cs index 2ed1a771ff..391c5e727a 100644 --- a/src/Tgstation.Server.Host/Components/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/IDmbFactory.cs @@ -7,18 +7,18 @@ namespace Tgstation.Server.Host.Components /// Factory for s /// interface IDmbFactory - { + { + /// + /// Get a that completes when the result of a call to will be different than the previous call if any + /// + /// A representing the running operation + Task OnNewerDmb { get; } + /// /// Gets the next /// /// The for the operation /// A representing the running operation Task LockNextDmb(CancellationToken cancellationToken); - - /// - /// Get a that completes when the result of a call to will be different than the previous call if any - /// - /// A representing the running operation - Task OnNewerDmb(); } } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index ef150c8552..ff78b157b8 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -53,6 +53,7 @@ namespace Tgstation.Server.Host.Components this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); instances = new Dictionary(); + interopConsumers = new Dictionary(); } /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/Watchdog/IReattachInfoHandler.cs new file mode 100644 index 0000000000..a5994d1d5b --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/IReattachInfoHandler.cs @@ -0,0 +1,26 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// Handles saving and loading + /// + interface IReattachInfoHandler + { + /// + /// Save some + /// + /// The to save + /// The for the operation + /// A representing the running operation + Task Save(WatchdogReattachInformation reattachInformation, CancellationToken cancellationToken); + + /// + /// Load a saved + /// + /// The for the operation + /// A resulting in the stored if any + Task Load(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs index 016d9f57d0..539181995f 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace Tgstation.Server.Host.Components.Watchdog { @@ -21,5 +22,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The peak virtual memory usage in bytes /// public long PeakMemory { get; set; } + + /// + public override string ToString() => String.Format(CultureInfo.InvariantCulture, "Exit Code: {0}, RAM: {1}, Time {2}ms", ExitCode, PeakMemory, StartupTime.TotalMilliseconds); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs new file mode 100644 index 0000000000..3140b9e854 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs @@ -0,0 +1,13 @@ +namespace Tgstation.Server.Host.Components.Watchdog +{ + /// + /// The action for the monitor loop to take when control is returned to it + /// + enum MonitorAction + { + Continue, + Break, + Restart, + Exit + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs index dfb418f9f4..09f24d30c5 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs @@ -5,6 +5,8 @@ public bool RebootingInactiveServer { get; set; } public bool InactiveServerHasStagedDmb { get; set; } + public MonitorAction NextAction { get; set; } + public ISessionController ActiveServer { get; set; } public ISessionController InactiveServer { get; set; } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 28bd36126b..88c4e1d662 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -53,7 +53,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public bool ClosePortsOnReboot { get; set; } + public bool ClosePortOnReboot { get; set; } /// public bool ApiValidated @@ -104,6 +104,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public Task Lifetime => session.Lifetime; + /// + public Task OnReboot => rebootTcs.Task; + /// /// The up to date /// @@ -142,6 +145,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The port to assign DreamDaemon when it queries for it /// ushort nextPort; + + /// + /// The that completes when DD tells us about a reboot + /// + TaskCompletionSource rebootTcs; /// /// If we know DreamDaemon currently has it's port closed @@ -181,6 +189,8 @@ namespace Tgstation.Server.Host.Components.Watchdog portClosed = false; disposed = false; apiValidated = false; + + rebootTcs = new TaskCompletionSource(); } /// @@ -229,13 +239,16 @@ namespace Tgstation.Server.Host.Components.Watchdog apiValidated = true; break; case DMCommandWorldReboot: - if (ClosePortsOnReboot) + if (ClosePortOnReboot) { content = new Dictionary { { DMParameterNewPort, 0 } }; portClosed = true; } else - ClosePortsOnReboot = true; + ClosePortOnReboot = true; + var oldTcs = rebootTcs; + rebootTcs = new TaskCompletionSource(); + oldTcs.SetResult(null); break; default: status = HttpStatusCode.BadRequest; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index aae84ea84a..b829806d86 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -60,6 +60,31 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IChat chat; + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + public SessionControllerFactory(IExecutor executor, IByond byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IInstance instance, IIOManager ioManager, IChat chat) + { + this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); + this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); + this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); + this.interopRegistrar = interopRegistrar ?? throw new ArgumentNullException(nameof(interopRegistrar)); + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); + } + /// public async Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken) { @@ -139,15 +164,46 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch { - chatJsonTrackingTask.Dispose(); + chatJsonTrackingContext.Dispose(); throw; } } /// - public Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken) + public async Task Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken) { - throw new NotImplementedException(); + if (reattachInformation == null) + throw new ArgumentNullException(nameof(reattachInformation)); + + var basePath = reattachInformation.IsPrimary ? reattachInformation.Dmb.PrimaryDirectory : reattachInformation.Dmb.SecondaryDirectory; + var chatJsonTrackingContext = await chat.TrackJsons(basePath, reattachInformation.ChatChannelsJson, reattachInformation.ChatCommandsJson, cancellationToken).ConfigureAwait(false); + try + { + var byondLock = byond.UseExecutables(reattachInformation.Dmb.CompileJob.ByondVersion); + try + { + var session = executor.AttachToDreamDaemon(reattachInformation.ProcessId, byondLock); + try + { + return new SessionController(reattachInformation, session, byondTopicSender, interopRegistrar, chatJsonTrackingContext, chat); + } + catch + { + session.Dispose(); + throw; + } + } + catch + { + byondLock.Dispose(); + throw; + } + } + catch + { + chatJsonTrackingContext.Dispose(); + throw; + } } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 5bf055c988..bc79f36450 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -1,16 +1,22 @@ -using System; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Core; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Watchdog { /// sealed class Watchdog : IWatchdog { + /// + /// The time in milliseconds to wait from starting to start . Does not take responsiveness into account + /// + const int AlphaBravoStartupSeperationInterval = 3000; + /// public bool Running { get; private set; } @@ -33,7 +39,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; } /// - public RebootState? RebootState => Running ? (RebootState?)(AlphaIsActive ? alphaServer.RebootState : bravoServer.RebootState) : null; + public RebootState? RebootState => Running ? (RebootState?)(AlphaIsActive ? alphaServer?.RebootState : bravoServer?.RebootState) : null; /// /// The for the @@ -45,21 +51,21 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly ISessionControllerFactory sessionControllerFactory; - /// - /// The for the - /// - readonly IEventConsumer eventConsumer; - - /// - /// The for the - /// - readonly IInteropRegistrar interopRegistrar; - /// /// The for the /// readonly IDmbFactory dmbFactory; + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// The for the + /// + readonly IReattachInfoHandler reattachInfoHandler; + /// /// The for the /// @@ -82,19 +88,19 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - /// The value of - /// The value of /// The for the + /// The value of + /// The value of /// The initial value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar, IServerUpdater serverUpdater, DreamDaemonLaunchParameters initialLaunchParameters) + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, DreamDaemonLaunchParameters initialLaunchParameters) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); - this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); - this.interopRegistrar = interopRegistrar ?? throw new ArgumentNullException(nameof(interopRegistrar)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); - if(serverUpdater == null) + if (serverUpdater == null) throw new ArgumentNullException(nameof(serverUpdater)); serverUpdater.RegisterForUpdate(() => releaseServers = true); @@ -112,9 +118,9 @@ namespace Tgstation.Server.Host.Components.Watchdog semaphore.Dispose(); } - void DisposeAndNullControllers() { + logger.LogTrace("DisposeAndNullControllers"); alphaServer?.Dispose(); alphaServer = null; bravoServer?.Dispose(); @@ -126,9 +132,17 @@ namespace Tgstation.Server.Host.Components.Watchdog var running = Running; if (!graceful || !running) { + Task chatTask; if (running) - await Terminate(false, cancellationToken).ConfigureAwait(false); - return await Launch(cancellationToken).ConfigureAwait(false); + { + chatTask = chat.SendWatchdogMessage("Manual restart triggered...", cancellationToken); + await TerminateNoLock(false, false, cancellationToken).ConfigureAwait(false); + } + else + chatTask = Task.CompletedTask; + var result = await LaunchNoLock(true, !running, false, cancellationToken).ConfigureAwait(false); + await chatTask.ConfigureAwait(false); + return result; } var toReboot = AlphaIsActive ? alphaServer : bravoServer; var other = AlphaIsActive ? bravoServer : alphaServer; @@ -138,53 +152,167 @@ namespace Tgstation.Server.Host.Components.Watchdog return null; } - async Task HandlerMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState) + async Task TerminateNoLock(bool graceful, bool announce, CancellationToken cancellationToken) { - + if (!Running) + return; + if (!graceful) + { + var chatTask = announce ? chat.SendWatchdogMessage("Terminating...", cancellationToken) : Task.CompletedTask; + await StopMonitor().ConfigureAwait(false); + DisposeAndNullControllers(); + await chatTask.ConfigureAwait(false); + return; + } + var toKill = AlphaIsActive ? alphaServer : bravoServer; + var other = AlphaIsActive ? bravoServer : alphaServer; + if (toKill != null) + await toKill.SetRebootState(Components.Watchdog.RebootState.Shutdown, cancellationToken).ConfigureAwait(false); } + async Task HandlerMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState) + { + logger.LogInformation("Monitor activation. Reason: {0}", activationReason); + await Task.Yield(); + } + + /// + /// The loop that watches the watchdog + /// + /// The for the operation + /// A representing the running operation async Task MonitorLifetimes(CancellationToken cancellationToken) { - var state = new MonitorState(); - while(true) + logger.LogDebug("Entered MonitorLifetimes"); + var iteration = 1; + for(var state = new MonitorState(); state.NextAction != MonitorAction.Exit; ++iteration) { + logger.LogDebug("New iteration of monitor loop"); try { - var alphaServerTask = alphaServer.Lifetime; - var bravoServerTask = bravoServer.Lifetime; - var alphaServerReboot = alphaServer.OnReboot; - var bravoServerReboot = bravoServer.OnReboot; + if(AlphaIsActive) + logger.LogDebug("Alpha is the active server"); + else + logger.LogDebug("Bravo is the active server"); - var activeServer = AlphaIsActive ? alphaServer : bravoServer; - var inactiveServer = AlphaIsActive ? bravoServer : alphaServer; + if(state.InactiveServerHasStagedDmb) + logger.LogDebug("Inactive server has staged .dmb"); + if (state.RebootingInactiveServer) + logger.LogDebug("Inactive server is rebooting"); + + state.ActiveServer = AlphaIsActive ? alphaServer : bravoServer; + state.InactiveServer = AlphaIsActive ? bravoServer : alphaServer; + + var activeServerLifetime = state.ActiveServer.Lifetime; + var inactiveServerLifetime = state.InactiveServer.Lifetime; + var activeServerReboot = state.ActiveServer.OnReboot; + var inactiveServerReboot = state.InactiveServer.OnReboot; + var inactiveServerStartup = state.InactiveServer.LaunchResult; + var newDmbAvailable = dmbFactory.OnNewerDmb; var cancelTcs = new TaskCompletionSource(); using (cancellationToken.Register(() => cancelTcs.SetCanceled())) { - var toWaitOn = Task.WhenAny(alphaServerTask, bravoServerTask, alphaServerReboot, bravoServerReboot, cancelTcs.Task); - if (!watchReboot) - toWaitOn = Task.WhenAny(toWaitOn, inactiveServer.LaunchResult); + var toWaitOn = Task.WhenAny(activeServerLifetime, inactiveServerLifetime, activeServerReboot, inactiveServerReboot, newDmbAvailable, cancelTcs.Task); + if (state.RebootingInactiveServer) + toWaitOn = Task.WhenAny(toWaitOn, inactiveServerStartup); await toWaitOn.ConfigureAwait(false); } - using (await SemaphoreContext.Lock(semaphore, default).ConfigureAwait(false)) + var chatTask = Task.CompletedTask; + using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { - state.ActiveServer = AlphaIsActive ? alphaServer : bravoServer; - state.InactiveServer = !AlphaIsActive ? alphaServer : bravoServer; - MonitorActivationReason activationReason; - if (activeServer.Lifetime.IsCompleted) - activationReason = MonitorActivationReason.ActiveServerCrashed; - else if(inactiveServer.Lifetime.IsCompleted) + MonitorActivationReason activationReason = default; + //multiple things may have happened, handle them one at a time + for (var moreActivationsToProcess = true; moreActivationsToProcess && state.NextAction == MonitorAction.Continue; await HandlerMonitorWakeup(activationReason, state).ConfigureAwait(false)) + { + if (activeServerLifetime?.IsCompleted == true) + { + activationReason = MonitorActivationReason.ActiveServerCrashed; + activeServerLifetime = null; + } + else if (inactiveServerLifetime?.IsCompleted == true) + { + activationReason = MonitorActivationReason.InactiveServerCrashed; + inactiveServerLifetime = null; + } + else if (activeServerReboot?.IsCompleted == true) + { + activationReason = MonitorActivationReason.ActiveServerRebooted; + activeServerReboot = null; + } + else if (inactiveServerReboot?.IsCompleted == true) + { + activationReason = MonitorActivationReason.InactiveServerRebooted; + inactiveServerReboot = null; + } + else if (inactiveServerStartup?.IsCompleted == true) + { + activationReason = MonitorActivationReason.InactiveServerStartupComplete; + inactiveServerStartup = null; + } + else if (newDmbAvailable?.IsCompleted == true) + { + activationReason = MonitorActivationReason.NewDmbAvailable; + newDmbAvailable = null; + } + else + moreActivationsToProcess = false; + } + //full reboot required + if (state.NextAction == MonitorAction.Restart) + { + logger.LogDebug("Next state action is to restart"); + DisposeAndNullControllers(); + Running = false; + chatTask = chat.SendWatchdogMessage("Restarting due to complications...", cancellationToken); + } + } + + for (var retryAttempts = 1; state.NextAction == MonitorAction.Restart; ++retryAttempts) + { + WatchdogLaunchResult result; + using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + result = await LaunchNoLock(false, false, false, cancellationToken).ConfigureAwait(false); + + await chatTask.ConfigureAwait(false); + if (Running) + state.NextAction = MonitorAction.Continue; + else + { + logger.LogWarning("Failed to automatically restart the watchdog! Alpha: {0}; Bravo: {1}", result.Alpha.ToString(), result.Bravo.ToString()); + var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); //max of one hour + chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Failed to restart watchdog (Attempt: {0}), retrying in {1} seconds...", retryAttempts, retryDelay), cancellationToken); + await Task.WhenAll(Task.Delay((int)retryDelay, cancellationToken), chatTask).ConfigureAwait(false); + } } } - catch (OperationCanceledException) { } + catch (OperationCanceledException) + { + logger.LogDebug("Monitor cancelled"); + break; + } catch (Exception e) { - await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Monitor crashed, this should NEVER happen! Restarting monitor... Error: {0}", e.Message), cancellationToken).ConfigureAwait(false); + logger.LogError("Monitor crashed! Iteration: {0}, State: {1}", iteration, JsonConvert.SerializeObject(state)); + await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Monitor crashed, this should NEVER happen! Please report this, full details in logs! Restarting monitor... Error: {0}", e.Message), cancellationToken).ConfigureAwait(false); } } } + async Task StopMonitor() + { + logger.LogTrace("StopMonitor"); + if (monitorTask == null) + return false; + monitorCts.Cancel(); + await monitorTask.ConfigureAwait(false); + monitorCts.Dispose(); + monitorTask = null; + return true; + } + + /// public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { @@ -196,99 +324,134 @@ namespace Tgstation.Server.Host.Components.Watchdog } } - async Task StopMonitor() - { - if (monitorTask == null) - return; - monitorCts.Cancel(); - await monitorTask.ConfigureAwait(false); - monitorCts.Dispose(); - monitorTask = null; - } - - async Task LaunchNoLock(bool startMonitor, CancellationToken cancellationToken) + async Task LaunchNoLock(bool startMonitor, bool announce, bool doReattach, CancellationToken cancellationToken) { using (var alphaStartCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { + logger.LogTrace("Begin LaunchNoLock"); if (Running) + { + logger.LogTrace("Aborted due to already running!"); return null; + } + + + Task chatTask; + //this is necessary, the monitor could be in it's sleep loop trying to restart + if (startMonitor && await StopMonitor().ConfigureAwait(false)) + chatTask = chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", cancellationToken); + else if (announce) + chatTask = chat.SendWatchdogMessage("Starting...", cancellationToken); + else + chatTask = Task.CompletedTask; //start both servers LastLaunchParameters = ActiveLaunchParameters; - var dmbToUse = await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false); - Task alphaServerTask = null; - try { + //good ole sanity + if (alphaServer != null || bravoServer != null) + throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!"); + + WatchdogReattachInformation reattachInfo = doReattach ? await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false) : null; + var doesntNeedNewDmb = doReattach && reattachInfo.Alpha != null && reattachInfo.Bravo != null; + var dmbToUse = doesntNeedNewDmb ? null : await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false); + + Task alphaServerTask = null; try { - alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, true, true, false, alphaStartCts.Token); - //do a few seconds of delay so that any backends the servers use know that alpha came first - await Task.Delay(5000, cancellationToken).ConfigureAwait(false); - var bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, false, false, false, cancellationToken); - bravoServer = await bravoServerTask.ConfigureAwait(false); - alphaServer = await alphaServerTask.ConfigureAwait(false); + try + { + if (!doReattach || reattachInfo.Alpha == null) + alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, true, true, false, alphaStartCts.Token); + else + alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken); + //do a few seconds of delay so that any backends the servers use know that alpha came first + await Task.Delay(AlphaBravoStartupSeperationInterval, cancellationToken).ConfigureAwait(false); + Task bravoServerTask; + if (!doReattach || reattachInfo.Bravo == null) + bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, false, false, false, cancellationToken); + else + bravoServerTask = sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken); + + bravoServer = await bravoServerTask.ConfigureAwait(false); + alphaServer = await alphaServerTask.ConfigureAwait(false); + } + catch + { + if (alphaServerTask != null) + if (alphaServerTask.Status == TaskStatus.RanToCompletion) + alphaServer = await alphaServerTask.ConfigureAwait(false); + else + { + alphaStartCts.Cancel(); + try + { + alphaServer = await alphaServerTask.ConfigureAwait(false); + } + catch { } + } + throw; + } + + async Task CheckLaunch(ISessionController controller, string serverName) + { + var launch = await controller.LaunchResult.ConfigureAwait(false); + if (launch.ExitCode.HasValue) + //you killed us ray... + throw new Exception(String.Format(CultureInfo.InvariantCulture, "{1} server failed to start: {0}", launch.ToString(), serverName)); + return launch; + } + + var alphaLrt = CheckLaunch(alphaServer, "Alpha"); + var bravoLrt = CheckLaunch(bravoServer, "Bravo"); + //now we have two booting servers, get them up and running + var allTask = Task.WhenAll(alphaLrt, bravoLrt); + + //don't forget about the cancelationToken + var cancelTcs = new TaskCompletionSource(); + using (cancellationToken.Register(() => cancelTcs.SetCanceled())) + await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false); + + //both servers are now running, alpha is the active server, huzzah + AlphaIsActive = doReattach ? reattachInfo.AlphaIsActive : true; + LiveCompileJob = AlphaIsActive ? alphaServer.Dmb.CompileJob : bravoServer.Dmb.CompileJob; + LastLaunchResult = alphaLrt.Result; + StagedCompileJob = null; + Running = true; + + if (startMonitor) + { + await StopMonitor().ConfigureAwait(false); + monitorCts = new CancellationTokenSource(); + monitorTask = MonitorLifetimes(monitorCts.Token); + } + return new WatchdogLaunchResult + { + Alpha = alphaLrt.Result, + Bravo = bravoLrt.Result + }; } catch { - if (alphaServerTask != null) - if (alphaServerTask.Status == TaskStatus.RanToCompletion) - alphaServer = await alphaServerTask.ConfigureAwait(false); - else - { - alphaStartCts.Cancel(); - try - { - alphaServer = await alphaServerTask.ConfigureAwait(false); - } - catch { } - } + if (alphaServer == null && bravoServer == null) + dmbToUse.Dispose(); //guaranteed to not be null here + DisposeAndNullControllers(); throw; } - - async Task CheckLaunch(ISessionController controller, string serverName) - { - var launch = await controller.LaunchResult.ConfigureAwait(false); - if (launch.ExitCode.HasValue) - //you killed us ray... - throw new Exception(String.Format(CultureInfo.InvariantCulture, "{2} server failed to start: Exit Code: {0}, RAM: {1}, Runtime {3}ms", launch.ExitCode, launch.StartupTime, serverName, launch.StartupTime.TotalMilliseconds)); - return launch; - } - - var alphaLrt = CheckLaunch(alphaServer, "Alpha"); - var bravoLrt = CheckLaunch(bravoServer, "Bravo"); - //now we have two booting servers, get them up and running - var allTask = Task.WhenAll(alphaLrt, bravoLrt); - - //don't forget about the cancelationToken - var cancelTcs = new TaskCompletionSource(); - using (cancellationToken.Register(() => cancelTcs.SetCanceled())) - await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false); - - //both servers are now running, alpha is the active server, huzzah - LiveCompileJob = dmbToUse.CompileJob; - LastLaunchResult = alphaLrt.Result; - StagedCompileJob = null; - AlphaIsActive = true; - Running = true; - - if (startMonitor) - { - await StopMonitor().ConfigureAwait(false); - monitorCts = new CancellationTokenSource(); - monitorTask = MonitorLifetimes(monitorCts.Token); - } - - return new WatchdogLaunchResult - { - Alpha = alphaLrt.Result, - Bravo = bravoLrt.Result - }; } - catch + catch (Exception e) { - DisposeAndNullControllers(); + logger.LogWarning("Failed to start watchdog: {0}", e.ToString()); throw; } + finally + { + try + { + await chatTask.ConfigureAwait(false); + } + catch (OperationCanceledException) { } + } } } @@ -296,7 +459,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public async Task Launch(CancellationToken cancellationToken) { using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) - return await LaunchNoLock(true, cancellationToken).ConfigureAwait(false); + return await LaunchNoLock(true, true, false, cancellationToken).ConfigureAwait(false); } /// @@ -310,37 +473,23 @@ namespace Tgstation.Server.Host.Components.Watchdog public async Task Terminate(bool graceful, CancellationToken cancellationToken) { using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) - { - if (!Running) - return; - await StopMonitor().ConfigureAwait(false); - if (!graceful) - { - DisposeAndNullControllers(); - return; - } - var toKill = AlphaIsActive ? alphaServer : bravoServer; - var other = AlphaIsActive ? bravoServer : alphaServer; - if (toKill != null) - await toKill.SetRebootState(Components.Watchdog.RebootState.Shutdown, cancellationToken).ConfigureAwait(false); - } + await TerminateNoLock(graceful, true, cancellationToken).ConfigureAwait(false); } /// - public Task StartAsync(CancellationToken cancellationToken) => Launch(cancellationToken); + public Task StartAsync(CancellationToken cancellationToken) => LaunchNoLock(true, true, true, cancellationToken); /// - public Task StopAsync(CancellationToken cancellationToken) + public async Task StopAsync(CancellationToken cancellationToken) { if (releaseServers) { - ReattachInformation reattachInformation; - if (AlphaIsActive) - reattachInformation = alphaServer?.Release(); - else - reattachInformation = bravoServer?.Release(); + var reattachInformation = new WatchdogReattachInformation { AlphaIsActive = AlphaIsActive }; + reattachInformation.Alpha = alphaServer?.Release(); + reattachInformation.Bravo = bravoServer?.Release(); + await reattachInfoHandler.Save(reattachInformation, cancellationToken).ConfigureAwait(false); } - return Terminate(false, cancellationToken); + await Terminate(false, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index dc93621c80..638f5f6940 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Core; @@ -7,11 +8,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// sealed class WatchdogFactory : IWatchdogFactory { - /// - /// The for the - /// - readonly IByond byond; - /// /// The for the /// @@ -22,40 +18,40 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly ISessionControllerFactory sessionManagerFactory; - /// - /// The for the - /// - readonly IEventConsumer eventConsumer; - - /// - /// The for the - /// - readonly IInteropRegistrar interopRegistrar; - /// /// The for the /// readonly IServerUpdater serverUpdater; + /// + /// The for the + /// + readonly ILoggerFactory loggerFactory; + + /// + /// The for the + /// + readonly IReattachInfoHandler reattachInfoHandler; + /// /// Construct a /// /// The value of /// The value of /// The value of - /// The value of - /// The value of /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionManagerFactory, IEventConsumer eventConsumer, IInteropRegistrar interopRegistrar, IServerUpdater serverUpdater) + /// The value of + /// The value of + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionManagerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); - this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); - this.interopRegistrar = interopRegistrar ?? throw new ArgumentNullException(nameof(interopRegistrar)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters) => new Watchdog(chat, sessionManagerFactory, dmbFactory, eventConsumer, interopRegistrar, serverUpdater, launchParameters); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters) => new Watchdog(chat, sessionManagerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, launchParameters); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs index 3109a1ef2b..6e63099862 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs @@ -8,16 +8,16 @@ /// /// If the Alpha session is the active session /// - bool AlphaIsActive { get; set; } + public bool AlphaIsActive { get; set; } /// /// for the Alpha session /// - ReattachInformation Alpha { get; set; } + public ReattachInformation Alpha { get; set; } /// /// for the Bravo session /// - ReattachInformation Bravo { get; set; } + public ReattachInformation Bravo { get; set; } } } diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index afd32b4923..519d590b0d 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.IO; using System.Threading.Tasks; @@ -9,7 +10,9 @@ namespace Tgstation.Server.Host.Core.Tests [TestClass] public sealed class TestApplication : IServerUpdater { - public void ApplyUpdate(string updatePath) => throw new System.NotImplementedException(); + public void ApplyUpdate(string updatePath) => throw new NotImplementedException(); + + public void RegisterForUpdate(Action action) => throw new NotImplementedException(); [TestMethod] public async Task TestSuccessfulStartup() From 1145d08b72757653e9ac644ee8e3bdd4987ce59f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 6 Jul 2018 13:08:07 -0400 Subject: [PATCH 180/397] The chains rattle, dashing closer, faster, tighter... --- .../Rights/DreamDaemonRights.cs | 4 +- .../Components/DmbFactory.cs | 33 ++++++++-- .../Components/IDmbFactory.cs | 14 +++- .../Components/IDmbProvider.cs | 2 +- .../Components/IInstance.cs | 2 +- .../Components/Instance.cs | 51 +++++++++++++- .../Watchdog/IReattachInfoHandler.cs | 2 +- .../Watchdog/ReattachInformation.cs | 43 ++---------- .../Components/Watchdog/SessionController.cs | 2 +- .../Watchdog/WatchdogReattachInformation.cs | 21 ++++-- .../Models/DatabaseContext.cs | 10 +++ src/Tgstation.Server.Host/Models/Instance.cs | 5 ++ .../Models/ReattachInformation.cs | 18 +++++ .../Models/ReattachInformationBase.cs | 66 +++++++++++++++++++ .../Models/WatchdogReattachInformation.cs | 27 ++++++++ .../Models/WatchdogReattachInformationBase.cs | 18 +++++ 16 files changed, 259 insertions(+), 59 deletions(-) create mode 100644 src/Tgstation.Server.Host/Models/ReattachInformation.cs create mode 100644 src/Tgstation.Server.Host/Models/ReattachInformationBase.cs create mode 100644 src/Tgstation.Server.Host/Models/WatchdogReattachInformation.cs create mode 100644 src/Tgstation.Server.Host/Models/WatchdogReattachInformationBase.cs diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index cd18f5ba53..0bcf055335 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -56,7 +56,9 @@ namespace Tgstation.Server.Api.Rights /// User can start and disable and /// Start = 1024, - + /// + /// User can change + /// SetStartupTimeout = 2048 } } diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index e16506e3ab..40d50d393a 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -43,11 +44,13 @@ namespace Tgstation.Server.Host.Components /// /// resulting in the latest yet to exist /// - TaskCompletionSource newerDmbTcs; + TaskCompletionSource newerDmbTcs; /// /// The latest /// - DmbProvider nextDmbProvider; + IDmbProvider nextDmbProvider; + + Dictionary jobLockCounts; /// /// Construct a @@ -60,6 +63,7 @@ namespace Tgstation.Server.Host.Components this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); cleanupCts = new CancellationTokenSource(); + jobLockCounts = new Dictionary(); } /// @@ -80,7 +84,11 @@ namespace Tgstation.Server.Host.Components await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(false); } lock (this) - cleanupTask = HandleCleanup(); + { + var currentVal = jobLockCounts[job.Id]; + if (--jobLockCounts[job.Id] == 0) + cleanupTask = HandleCleanup(); + } } /// @@ -95,16 +103,16 @@ namespace Tgstation.Server.Host.Components var oldDmbProvider = nextDmbProvider; if (oldDmbProvider != null && oldDmbProvider.CompileJob.Job.StoppedAt < oldDmbProvider.CompileJob.Job.StoppedAt) throw new InvalidOperationException("Loaded compile job older than current job!"); - nextDmbProvider = new DmbProvider(job, ioManager, () => CleanJob(job)); + nextDmbProvider = FromCompileJob(job); newerDmbTcs.SetResult(nextDmbProvider); - newerDmbTcs = new TaskCompletionSource(); + newerDmbTcs = new TaskCompletionSource(); } } /// public async Task LockNextDmb(CancellationToken cancellationToken) { - Task task; + Task task; lock (this) if (nextDmbProvider != null) return nextDmbProvider; @@ -137,5 +145,18 @@ namespace Tgstation.Server.Host.Components using (cancellationToken.Register(() => cleanupCts.Cancel())) await cleanupTask.ConfigureAwait(false); } + + /// + public IDmbProvider FromCompileJob(CompileJob compileJob) + { + lock (this) + { + if (!jobLockCounts.TryGetValue(compileJob.Id, out int value)) + jobLockCounts.Add(compileJob.Id, 1); + else + jobLockCounts[compileJob.Id] = ++value; + return new DmbProvider(compileJob, ioManager, () => CleanJob(compileJob)); + } + } } } diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/IDmbFactory.cs index 391c5e727a..378c9d9860 100644 --- a/src/Tgstation.Server.Host/Components/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/IDmbFactory.cs @@ -1,12 +1,13 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { /// /// Factory for s /// - interface IDmbFactory + public interface IDmbFactory { /// /// Get a that completes when the result of a call to will be different than the previous call if any @@ -18,7 +19,14 @@ namespace Tgstation.Server.Host.Components /// Gets the next /// /// The for the operation - /// A representing the running operation + /// A resulting in a new Task LockNextDmb(CancellationToken cancellationToken); - } + + /// + /// Gets a for a given + /// + /// The to make the for + /// A new + IDmbProvider FromCompileJob(CompileJob compileJob); + } } diff --git a/src/Tgstation.Server.Host/Components/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/IDmbProvider.cs index 7ccb67316f..75bc533409 100644 --- a/src/Tgstation.Server.Host/Components/IDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/IDmbProvider.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Components /// /// Provides absolute paths to the latest compiled .dmbs /// - interface IDmbProvider : IDisposable + public interface IDmbProvider : IDisposable { /// /// The file name of the .dmb diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index faa3c99de7..3f8beaadb4 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Components /// /// For interacting with the instance services /// - public interface IInstance : IHostedService + public interface IInstance : IHostedService, IReattachInfoHandler { /// /// The for the diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 04554e81ec..664b776be8 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -38,7 +38,12 @@ namespace Tgstation.Server.Host.Components /// The for the /// readonly IDatabaseContextFactory databaseContextFactory; - + + /// + /// The for the + /// + readonly IDmbFactory dmbFactory; + /// /// The for the /// @@ -53,7 +58,7 @@ namespace Tgstation.Server.Host.Components /// CancellationTokenSource timerCts; - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory) + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); @@ -64,6 +69,7 @@ namespace Tgstation.Server.Host.Components Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); } /// @@ -150,5 +156,46 @@ namespace Tgstation.Server.Host.Components timerTask = TimerLoop(newInterval.Value, timerCts.Token); } } + + /// + public Task Save(WatchdogReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => + { + var instance = new Models.Instance { Id = metadata.Id }; + db.Instances.Attach(instance); + + Models.ReattachInformation ConvertReattachInfo(ReattachInformation wdInfo) + { + db.CompileJobs.Attach(wdInfo.Dmb.CompileJob); + return new Models.ReattachInformation + { + AccessIdentifier = wdInfo.AccessIdentifier, + ChatChannelsJson = wdInfo.ChatChannelsJson, + ChatCommandsJson = wdInfo.ChatCommandsJson, + CompileJob = wdInfo.Dmb.CompileJob, + IsPrimary = wdInfo.IsPrimary, + Port = wdInfo.Port, + ProcessId = wdInfo.ProcessId, + RebootState = wdInfo.RebootState + }; + } + + instance.WatchdogReattachInformation = new Models.WatchdogReattachInformation + { + Alpha = ConvertReattachInfo(reattachInformation.Alpha), + Bravo = ConvertReattachInfo(reattachInformation.Bravo), + AlphaIsActive = reattachInformation.AlphaIsActive, + }; + await db.Save(cancellationToken).ConfigureAwait(false); + }); + + /// + public async Task Load(CancellationToken cancellationToken) + { + Models.WatchdogReattachInformation result = null; + await databaseContextFactory.UseContext(async (db) => + result = await db.Instances.Where(x => x.Id == metadata.Id).Select(x => x.WatchdogReattachInformation).FirstAsync(cancellationToken).ConfigureAwait(false) + ).ConfigureAwait(false); + return new WatchdogReattachInformation(result, dmbFactory); + } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/Watchdog/IReattachInfoHandler.cs index a5994d1d5b..3547b641c7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IReattachInfoHandler.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IReattachInfoHandler.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Handles saving and loading /// - interface IReattachInfoHandler + public interface IReattachInfoHandler { /// /// Save some diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs index 4cd93191ad..16c1319ba3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs @@ -1,50 +1,21 @@ -using System; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Watchdog { /// /// Parameters necessary for duplicating a session /// - sealed class ReattachInformation + public sealed class ReattachInformation : ReattachInformationBase { - /// - /// Used to identify and authenticate the DreamDaemon instance - /// - public string AccessIdentifier { get; set; } - - /// - /// The system process ID - /// - public int ProcessId { get; set; } - - /// - /// If the of is being used - /// - public bool IsPrimary { get; set; } - - /// - /// The port DreamDaemon was last listening on - /// - public ushort Port { get; set; } - - /// - /// The current DreamDaemon reboot state - /// - public RebootState RebootState { get; set; } - /// /// The used by DreamDaemon /// public IDmbProvider Dmb { get; set; } - - /// - /// Path to the chat commands json file - /// - public string ChatCommandsJson { get; set; } - /// - /// Path to the chat channels json file - /// - public string ChatChannelsJson { get; set; } + public ReattachInformation() { } + public ReattachInformation(Models.ReattachInformation copy, IDmbFactory dmbFactory) : base(copy) + { + Dmb = dmbFactory.FromCompileJob(copy.CompileJob); + } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 88c4e1d662..d86f474ce1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// sealed class SessionController : ISessionController, IInteropConsumer { - //interop values, match them up with tgs.dm and the appropriate api.dm + //interop values, match them up with the appropriate api.dm //api version 4.0.0.0 const string DMParamInfoJson = "tgs_json"; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs index 6e63099862..a43408e6b4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs @@ -1,15 +1,13 @@ -namespace Tgstation.Server.Host.Components.Watchdog +using System; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components.Watchdog { /// /// Reattach information for a /// - sealed class WatchdogReattachInformation + public sealed class WatchdogReattachInformation : WatchdogReattachInformationBase { - /// - /// If the Alpha session is the active session - /// - public bool AlphaIsActive { get; set; } - /// /// for the Alpha session /// @@ -19,5 +17,14 @@ /// for the Bravo session /// public ReattachInformation Bravo { get; set; } + + public WatchdogReattachInformation() { } + public WatchdogReattachInformation(Models.WatchdogReattachInformation copy, IDmbFactory dmbFactory): base(copy) + { + if (copy.Alpha != null) + Alpha = new ReattachInformation(copy.Alpha, dmbFactory); + if (copy.Bravo != null) + Bravo = new ReattachInformation(copy.Bravo, dmbFactory); + } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 8e6d4617ec..2229e1c9da 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -67,6 +67,16 @@ namespace Tgstation.Server.Host.Models /// public DbSet TestMerges { get; set; } + /// + /// The for s + /// + public DbSet ReattachInformation { get; set; } + + /// + /// The for s + /// + public DbSet WatchdogReattachInformation { get; set; } + /// /// The connection string for the /// diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index ca3a5bfad5..a9d82058d2 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -27,6 +27,11 @@ namespace Tgstation.Server.Host.Models /// public RepositorySettings RepositorySettings { get; set; } + /// + /// The for the + /// + public WatchdogReattachInformation WatchdogReattachInformation { get; set; } + /// /// The s in the /// diff --git a/src/Tgstation.Server.Host/Models/ReattachInformation.cs b/src/Tgstation.Server.Host/Models/ReattachInformation.cs new file mode 100644 index 0000000000..cc2dcbdd06 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/ReattachInformation.cs @@ -0,0 +1,18 @@ +namespace Tgstation.Server.Host.Models +{ + /// + /// Database representation of + /// + public sealed class ReattachInformation : ReattachInformationBase + { + /// + /// The row Id + /// + public long Id { get; set; } + + /// + /// The for the + /// + public CompileJob CompileJob { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs new file mode 100644 index 0000000000..defb570cd6 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs @@ -0,0 +1,66 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Host.Components.Watchdog; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Base class for + /// + public class ReattachInformationBase + { + /// + /// Used to identify and authenticate the DreamDaemon instance + /// + [Required] + public string AccessIdentifier { get; set; } + + /// + /// The system process ID + /// + public int ProcessId { get; set; } + + /// + /// If the of is being used + /// + public bool IsPrimary { get; set; } + + /// + /// The port DreamDaemon was last listening on + /// + public ushort Port { get; set; } + + /// + /// The current DreamDaemon reboot state + /// + [Required] + public RebootState RebootState { get; set; } + + /// + /// Path to the chat commands json file + /// + [Required] + public string ChatCommandsJson { get; set; } + + /// + /// Path to the chat channels json file + /// + [Required] + public string ChatChannelsJson { get; set; } + + public ReattachInformationBase() { } + + protected ReattachInformationBase(ReattachInformationBase copy) + { + if (copy == null) + throw new ArgumentNullException(nameof(copy)); + AccessIdentifier = copy.AccessIdentifier; + ChatChannelsJson = copy.ChatChannelsJson; + ChatCommandsJson = copy.ChatCommandsJson; + IsPrimary = copy.IsPrimary; + Port = copy.Port; + ProcessId = copy.ProcessId; + RebootState = copy.RebootState; + } + } +} diff --git a/src/Tgstation.Server.Host/Models/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Models/WatchdogReattachInformation.cs new file mode 100644 index 0000000000..db27049745 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/WatchdogReattachInformation.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Database representation of + /// + public sealed class WatchdogReattachInformation : WatchdogReattachInformationBase + { + /// + /// The row Id + /// + public long Id { get; set; } + + /// + /// The for the Alpha server + /// + public ReattachInformation Alpha { get; set; } + + /// + /// The for the Bravo server + /// + public ReattachInformation Bravo { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/WatchdogReattachInformationBase.cs b/src/Tgstation.Server.Host/Models/WatchdogReattachInformationBase.cs new file mode 100644 index 0000000000..2d578f2915 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/WatchdogReattachInformationBase.cs @@ -0,0 +1,18 @@ +using System; + +namespace Tgstation.Server.Host.Models +{ + public class WatchdogReattachInformationBase + { + /// + /// If the Alpha session is the active session + /// + public bool AlphaIsActive { get; set; } + + public WatchdogReattachInformationBase() { } + protected WatchdogReattachInformationBase(WatchdogReattachInformationBase copy) + { + AlphaIsActive = copy?.AlphaIsActive ?? throw new ArgumentNullException(nameof(copy)); + } + } +} From f31befa3128173ae5a4d5e57294e4251d1125a0c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 6 Jul 2018 21:27:37 -0400 Subject: [PATCH 181/397] Let me at least push this --- .../Components/{ => Chat}/ChatResponse.cs | 2 +- .../Components/{ => Chat}/IChat.cs | 2 +- .../{ => Chat}/IChatJsonTrackingContext.cs | 2 +- .../Components/ChatMessageEventArgs.cs | 15 ------ .../Components/Watchdog/InteropConstants.cs | 38 ++++++++++++++ .../Components/Watchdog/SessionController.cs | 49 +++++-------------- .../Watchdog/SessionControllerFactory.cs | 3 +- .../Components/Watchdog/Watchdog.cs | 25 +++++++++- 8 files changed, 78 insertions(+), 58 deletions(-) rename src/Tgstation.Server.Host/Components/{ => Chat}/ChatResponse.cs (76%) rename src/Tgstation.Server.Host/Components/{ => Chat}/IChat.cs (98%) rename src/Tgstation.Server.Host/Components/{ => Chat}/IChatJsonTrackingContext.cs (62%) delete mode 100644 src/Tgstation.Server.Host/Components/ChatMessageEventArgs.cs create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/InteropConstants.cs diff --git a/src/Tgstation.Server.Host/Components/ChatResponse.cs b/src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs similarity index 76% rename from src/Tgstation.Server.Host/Components/ChatResponse.cs rename to src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs index d79f8f4a7f..e7bc242642 100644 --- a/src/Tgstation.Server.Host/Components/ChatResponse.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Chat { sealed class ChatResponse { diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/Chat/IChat.cs similarity index 98% rename from src/Tgstation.Server.Host/Components/IChat.cs rename to src/Tgstation.Server.Host/Components/Chat/IChat.cs index 843abf3164..f73805eb2b 100644 --- a/src/Tgstation.Server.Host/Components/IChat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChat.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Chat { /// /// For managing connected chat services diff --git a/src/Tgstation.Server.Host/Components/IChatJsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs similarity index 62% rename from src/Tgstation.Server.Host/Components/IChatJsonTrackingContext.cs rename to src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs index b2c9e53e57..510f922617 100644 --- a/src/Tgstation.Server.Host/Components/IChatJsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs @@ -1,6 +1,6 @@ using System; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Chat { public interface IChatJsonTrackingContext : IDisposable { diff --git a/src/Tgstation.Server.Host/Components/ChatMessageEventArgs.cs b/src/Tgstation.Server.Host/Components/ChatMessageEventArgs.cs deleted file mode 100644 index 7e5839565a..0000000000 --- a/src/Tgstation.Server.Host/Components/ChatMessageEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Tgstation.Server.Host.Components -{ - /// - /// Represents a chat broadcast request - /// - sealed class ChatMessageEventArgs : EventArgs - { - /// - /// The to send out - /// - public ChatResponse ChatResponse { get; set; } - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/InteropConstants.cs b/src/Tgstation.Server.Host/Components/Watchdog/InteropConstants.cs new file mode 100644 index 0000000000..9b08efcdf5 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/InteropConstants.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tgstation.Server.Host.Components.Watchdog +{ + static class InteropConstants + { + //interop values, match them up with the appropriate api.dm + + //api version 4.0.0.0 + public const string DMParamHostVersion = "server_service_version"; + public const string DMParamInfoJson = "tgs_json"; + + public const string DMInteropAccessIdentifier = "tgs_tok"; + + public const string DMResponseSuccess = "tgs_succ"; + + public const string DMTopicChangePort = "tgs_port"; + public const string DMTopicChangeReboot = "tgs_rmode"; + public const string DMTopicChatCommand = "tgs_chat_comm"; + public const string DMTopicEvent = "tgs_event"; + + public const string DMCommandOnline = "tgs_on"; + public const string DMCommandIdentify = "tgs_ident"; + public const string DMCommandApiValidate = "tgs_validate"; + public const string DMCommandServerPrimed = "tgs_prime"; + public const string DMCommandWorldReboot = "tgs_reboot"; + public const string DMCommandEndProcess = "tgs_kill"; + public const string DMCommandChat = "tgs_chat_send"; + + public const string DMParameterCommand = "tgs_com"; + public const string DMParameterData = "tgs_data"; + + public const string DMParameterNewPort = "new_port"; + public const string DMParameterNewRebootMode = "new_rmode"; + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index d86f474ce1..43913c7e54 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -8,40 +8,13 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Chat; namespace Tgstation.Server.Host.Components.Watchdog { /// sealed class SessionController : ISessionController, IInteropConsumer { - //interop values, match them up with the appropriate api.dm - - //api version 4.0.0.0 - const string DMParamInfoJson = "tgs_json"; - - const string DMInteropAccessIdentifier = "tgs_tok"; - - const string DMResponseSuccess = "tgs_succ"; - - const string DMTopicChangePort = "tgs_port"; - const string DMTopicChangeReboot = "tgs_rmode"; - const string DMTopicChatCommand = "tgs_chat_comm"; - const string DMTopicEvent = "tgs_event"; - - const string DMCommandOnline = "tgs_on"; - const string DMCommandIdentify = "tgs_ident"; - const string DMCommandApiValidate = "tgs_validate"; - const string DMCommandServerPrimed = "tgs_prime"; - const string DMCommandWorldReboot = "tgs_reboot"; - const string DMCommandEndProcess = "tgs_kill"; - const string DMCommandChat = "tgs_chat_send"; - - const string DMParameterCommand = "tgs_com"; - const string DMParameterData = "tgs_data"; - - const string DMParameterNewPort = "new_port"; - const string DMParameterNewRebootMode = "new_rmode"; - /// public bool IsPrimary { @@ -217,15 +190,15 @@ namespace Tgstation.Server.Host.Components.Watchdog object content = new object(); var status = HttpStatusCode.OK; - if (query.TryGetValue(DMParameterCommand, out StringValues values)) + if (query.TryGetValue(InteropConstants.DMParameterCommand, out StringValues values)) switch (values.First()) { - case DMCommandIdentify: + case InteropConstants.DMCommandIdentify: lock (this) if (portClosed) - content = new Dictionary { { DMParameterNewPort, nextPort } }; + content = new Dictionary { { InteropConstants.DMParameterNewPort, nextPort } }; break; - case DMCommandOnline: + case InteropConstants.DMCommandOnline: lock (this) if (portClosed) { @@ -235,13 +208,13 @@ namespace Tgstation.Server.Host.Components.Watchdog portClosed = false; } break; - case DMCommandApiValidate: + case InteropConstants.DMCommandApiValidate: apiValidated = true; break; - case DMCommandWorldReboot: + case InteropConstants.DMCommandWorldReboot: if (ClosePortOnReboot) { - content = new Dictionary { { DMParameterNewPort, 0 } }; + content = new Dictionary { { InteropConstants.DMParameterNewPort, 0 } }; portClosed = true; } else @@ -281,9 +254,9 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic(new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), String.Format(CultureInfo.InvariantCulture, "?{0}={1}&{2}={3}", DMInteropAccessIdentifier, reattachInformation.AccessIdentifier, DMParameterCommand, command), cancellationToken); + public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic(new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), String.Format(CultureInfo.InvariantCulture, "?{0}={1}&{2}={3}", InteropConstants.DMInteropAccessIdentifier, reattachInformation.AccessIdentifier, InteropConstants.DMParameterCommand, command), cancellationToken); - async Task SetPortImpl(ushort port, CancellationToken cancellationToken) => await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", DMTopicChangePort, DMParameterNewPort, port), cancellationToken).ConfigureAwait(false) == DMResponseSuccess; + async Task SetPortImpl(ushort port, CancellationToken cancellationToken) => await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", InteropConstants.DMTopicChangePort, InteropConstants.DMParameterNewPort, port), cancellationToken).ConfigureAwait(false) == InteropConstants.DMResponseSuccess; /// public async Task ClosePort(CancellationToken cancellationToken) @@ -330,7 +303,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (RebootState == newRebootState) return true; - return await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", DMTopicChangeReboot, DMParameterNewRebootMode, (int)newRebootState), cancellationToken).ConfigureAwait(false) == DMResponseSuccess; + return await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", InteropConstants.DMTopicChangeReboot, InteropConstants.DMParameterNewRebootMode, (int)newRebootState), cancellationToken).ConfigureAwait(false) == InteropConstants.DMResponseSuccess; } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index b829806d86..19de65758f 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Security; @@ -135,7 +136,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var byondLock = byond.UseExecutables(dmbProvider.CompileJob.ByondVersion); try { - var parameters = String.Format(CultureInfo.InvariantCulture, "server_service_version={0}&tgs_json={1}", application.Version, interopJsonFile); + var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", application.Version, interopJsonFile, InteropConstants.DMParamHostVersion, InteropConstants.DMParamInfoJson); var session = executor.RunDreamDaemon(launchParameters, byondLock, dmbProvider, parameters, !primaryPort, !primaryDirectory); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index bc79f36450..1a3f05fcf2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -1,16 +1,19 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.Globalization; +using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class Watchdog : IWatchdog + sealed class Watchdog : IWatchdog, IEventConsumer { /// /// The time in milliseconds to wait from starting to start . Does not take responsiveness into account @@ -491,5 +494,25 @@ namespace Tgstation.Server.Host.Components.Watchdog } await Terminate(false, cancellationToken).ConfigureAwait(false); } + + /// + public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + { + using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + if (!Running) + return; + + var builder = new StringBuilder(InteropConstants.DMTopicEvent); + foreach (var I in parameters) + { + builder.Append("&"); + builder.Append(I); + } + + var activeServer = AlphaIsActive ? alphaServer : bravoServer; + await activeServer.SendCommand(builder.ToString(), cancellationToken).ConfigureAwait(false); + } + } } } From 1a1b1f4593f06f27b94a537dddf60affcd6d5156 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 9 Jul 2018 16:51:47 -0400 Subject: [PATCH 182/397] Unaccomplished commit --- .../Components/Instance.cs | 1 + .../Components/Watchdog/IWatchdog.cs | 3 ++ .../Components/Watchdog/Watchdog.cs | 1 + .../Controllers/DreamDaemonController.cs | 30 +++++++++++-------- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 664b776be8..29f6b1e1eb 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index b7ee4ce2fd..85e6594d9f 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -6,6 +6,9 @@ using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Watchdog { + /// + /// Runs and monitors the twin server controllers + /// public interface IWatchdog : IHostedService, IDisposable { /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 1a3f05fcf2..b03163fbc0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -177,6 +177,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { logger.LogInformation("Monitor activation. Reason: {0}", activationReason); await Task.Yield(); + throw new NotImplementedException(); } /// diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 356e52fb9d..18aab9a9fe 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Controllers /// /// for managing /// - [Route("/" + nameof(Api.Models.DreamDaemon))] + [Route("/" + nameof(DreamDaemon))] public sealed class DreamDaemonController : ModelController { /// @@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamDaemonRights.Start)] - public override async Task Create([FromBody] Api.Models.DreamDaemon model, CancellationToken cancellationToken) + public override async Task Create([FromBody] DreamDaemon model, CancellationToken cancellationToken) { var instance = instanceManager.GetInstance(Instance); @@ -125,7 +125,7 @@ namespace Tgstation.Server.Host.Controllers } /// - [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start)] + [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start | DreamDaemonRights.SetStartupTimeout)] public override async Task Update([FromBody] DreamDaemon model, CancellationToken cancellationToken) { var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); @@ -147,23 +147,29 @@ namespace Tgstation.Server.Host.Controllers return false; }; + var oldSoftRestart = current.SoftRestart; + var oldSoftShutdown = current.SoftShutdown; + if (!CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient) || !CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart) || !CheckModified(x => x.PrimaryPort, DreamDaemonRights.SetPorts) || !CheckModified(x => x.SecondaryPort, DreamDaemonRights.SetPorts) || !CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity) - || !CheckModified(x => x.SoftRestart, DreamDaemonRights.SoftRestart)) + || !CheckModified(x => x.SoftRestart, DreamDaemonRights.SoftRestart) + || !CheckModified(x => x.SoftShutdown, DreamDaemonRights.SoftShutdown) + || !CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)) return Forbid(); + + var wd = instanceManager.GetInstance(Instance).Watchdog; + await wd.ChangeSettings(current, cancellationToken).ConfigureAwait(false); - //interaction with soft stop is a bit different - if (model.SoftShutdown.HasValue) - { - if (current.SoftShutdown != model.SoftShutdown && ((!current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.SoftShutdown)) || (current.SoftShutdown.Value && !userRights.HasFlag(DreamDaemonRights.Start)))) - return Forbid(); - current.SoftShutdown = model.SoftShutdown; - } + //soft shutdown/restart can't be cancelled because of how many things rely on them + //They can be alternated though + if (!oldSoftRestart.Value && current.SoftRestart.Value) + await wd.Restart(true, cancellationToken).ConfigureAwait(false); + else if (!oldSoftShutdown.Value && current.SoftShutdown.Value) + await wd.Terminate(true, cancellationToken).ConfigureAwait(false); - await instanceManager.GetInstance(Instance).Watchdog.ChangeSettings(current, cancellationToken).ConfigureAwait(false); await DatabaseContext.Save(default).ConfigureAwait(false); return Ok(); From cf01b4c60a70139a14bdb493c52a3549e663e38e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Jul 2018 11:49:22 -0400 Subject: [PATCH 183/397] Slight async optimization to the DreamDaemonControlller Launch multiple tasks so they may execute concurrently before awaiting --- .../Controllers/DreamDaemonController.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 18aab9a9fe..816b13c8ca 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -161,14 +161,17 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); var wd = instanceManager.GetInstance(Instance).Watchdog; - await wd.ChangeSettings(current, cancellationToken).ConfigureAwait(false); + + var changeSettingsTask = wd.ChangeSettings(current, cancellationToken); - //soft shutdown/restart can't be cancelled because of how many things rely on them - //They can be alternated though - if (!oldSoftRestart.Value && current.SoftRestart.Value) - await wd.Restart(true, cancellationToken).ConfigureAwait(false); - else if (!oldSoftShutdown.Value && current.SoftShutdown.Value) - await wd.Terminate(true, cancellationToken).ConfigureAwait(false); + //soft shutdown/restart can't be cancelled because of how many things rely on them + //They can be alternated though + if (!oldSoftRestart.Value && current.SoftRestart.Value) + await Task.WhenAll(changeSettingsTask, wd.Restart(true, cancellationToken)).ConfigureAwait(false); + else if (!oldSoftShutdown.Value && current.SoftShutdown.Value) + await Task.WhenAll(changeSettingsTask, wd.Terminate(true, cancellationToken)).ConfigureAwait(false); + else + await changeSettingsTask.ConfigureAwait(false); await DatabaseContext.Save(default).ConfigureAwait(false); From 8ddb6adf91e93cafc5342487e904de23e3683afe Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Jul 2018 11:49:51 -0400 Subject: [PATCH 184/397] Tabify DreamDaemonController.cs --- .../Controllers/DreamDaemonController.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 816b13c8ca..e69a18aca7 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -161,17 +161,17 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); var wd = instanceManager.GetInstance(Instance).Watchdog; - + var changeSettingsTask = wd.ChangeSettings(current, cancellationToken); - //soft shutdown/restart can't be cancelled because of how many things rely on them - //They can be alternated though - if (!oldSoftRestart.Value && current.SoftRestart.Value) - await Task.WhenAll(changeSettingsTask, wd.Restart(true, cancellationToken)).ConfigureAwait(false); - else if (!oldSoftShutdown.Value && current.SoftShutdown.Value) - await Task.WhenAll(changeSettingsTask, wd.Terminate(true, cancellationToken)).ConfigureAwait(false); - else - await changeSettingsTask.ConfigureAwait(false); + //soft shutdown/restart can't be cancelled because of how many things rely on them + //They can be alternated though + if (!oldSoftRestart.Value && current.SoftRestart.Value) + await Task.WhenAll(changeSettingsTask, wd.Restart(true, cancellationToken)).ConfigureAwait(false); + else if (!oldSoftShutdown.Value && current.SoftShutdown.Value) + await Task.WhenAll(changeSettingsTask, wd.Terminate(true, cancellationToken)).ConfigureAwait(false); + else + await changeSettingsTask.ConfigureAwait(false); await DatabaseContext.Save(default).ConfigureAwait(false); From 59b855156b2a3da9169b99bbceb3411db8faa259 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Jul 2018 12:05:58 -0400 Subject: [PATCH 185/397] Fix release build --- .../Rights/DreamDaemonRights.cs | 2 +- .../Components/Chat/IChatJsonTrackingContext.cs | 3 +++ .../Components/DreamMaker.cs | 8 +++----- .../Components/IInstance.cs | 1 + .../Components/IInstanceManager.cs | 1 + .../Components/IInteropRegistrar.cs | 2 +- .../Components/Watchdog/IExecutor.cs | 2 +- .../Components/Watchdog/ISessionController.cs | 10 ++++++---- .../Components/Watchdog/LaunchResult.cs | 2 +- .../Components/Watchdog/ReattachInformation.cs | 9 +++++++++ .../Components/Watchdog/Watchdog.cs | 16 ++++++++-------- .../Components/Watchdog/WatchdogFactory.cs | 2 +- .../Watchdog/WatchdogReattachInformation.cs | 9 +++++++++ ...maphoreContext.cs => SemaphoreSlimContext.cs} | 10 +++++----- .../Models/DreamDaemonSettings.cs | 11 ++++++++--- .../Models/ReattachInformationBase.cs | 11 +++++++++-- .../Models/WatchdogReattachInformationBase.cs | 13 ++++++++++++- 17 files changed, 79 insertions(+), 33 deletions(-) rename src/Tgstation.Server.Host/Core/{SemaphoreContext.cs => SemaphoreSlimContext.cs} (80%) diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index 0bcf055335..86665af07b 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Api.Rights /// None = 0, /// - /// User can read + /// User can read and /// ReadRevision = 1, /// diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs index 510f922617..8926b33291 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs @@ -2,6 +2,9 @@ namespace Tgstation.Server.Host.Components.Chat { + /// + /// Represents a tracking of dynamic chat json files + /// public interface IChatJsonTrackingContext : IDisposable { } diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 2dafc3b983..e51b301ef0 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -67,8 +67,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - /// The value of - /// The value of + /// The value of /// The value of /// The value of /// @@ -85,12 +84,11 @@ namespace Tgstation.Server.Host.Components /// /// Run a quick DD instance to test the DMAPI is installed on the target code /// - /// The path to the DreamDaemon executable /// The timeout in seconds for validation /// The for the operation /// The for the operation /// A resulting in if the DMAPI was successfully validated, otherwise - async Task VerifyApi(string dreamDaemonPath, int timeout, Models.CompileJob job, CancellationToken cancellationToken) + async Task VerifyApi(int timeout, Models.CompileJob job, CancellationToken cancellationToken) { var launchParameters = new DreamDaemonLaunchParameters { @@ -268,7 +266,7 @@ namespace Tgstation.Server.Host.Components Status = CompilerStatus.Verifying; - ddVerified = job.ExitCode == 0 && await VerifyApi(byondLock.DreamDaemonPath, apiValidateTimeout, job, cancellationToken).ConfigureAwait(false); + ddVerified = job.ExitCode == 0 && await VerifyApi(apiValidateTimeout, job, cancellationToken).ConfigureAwait(false); } if (!ddVerified) diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 3f8beaadb4..54cd4468d6 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Hosting; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Watchdog; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index 1eb1454dd2..ca949b42a7 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -45,6 +45,7 @@ namespace Tgstation.Server.Host.Components /// Handle a GET via world/Export /// /// The request query + /// The for the operation /// A resulting in an graph that can be jsonified Task HandleWorldExport(IQueryCollection query, CancellationToken cancellationToken); } diff --git a/src/Tgstation.Server.Host/Components/IInteropRegistrar.cs b/src/Tgstation.Server.Host/Components/IInteropRegistrar.cs index 9298a67236..92f7d601dd 100644 --- a/src/Tgstation.Server.Host/Components/IInteropRegistrar.cs +++ b/src/Tgstation.Server.Host/Components/IInteropRegistrar.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Host.Components { /// - /// Creates s for + /// Creates s for s /// interface IInteropRegistrar { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs b/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs index b27a06a78f..84e2232bd3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs @@ -11,11 +11,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Run a dream daemon instance /// /// The - /// The that is completed when dream daemon starts without crashing /// The for the new /// The for the .dmb to run /// The value of the -params command line option /// If the field of should be used + /// If the field of should be used /// A new ISession RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, IByondExecutableLock byondLock, IDmbProvider dmbProvider, string parameters, bool useSecondaryPort, bool useSecondaryDirectory); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs index efae086297..949e1b53d9 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs @@ -44,7 +44,7 @@ namespace Tgstation.Server.Host.Components.Watchdog Task OnReboot { get; } /// - /// Releases the without terminating it. Also calls + /// Releases the without terminating it. Also calls /// /// which can be used to create a new similar to this one ReattachInformation Release(); @@ -52,20 +52,22 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Sends a command to DreamDaemon through /world/Topic() /// - /// The for the operation + /// The command to send + /// The for the operation /// A resulting in the result of /world/Topic() Task SendCommand(string command, CancellationToken cancellationToken); /// /// Closes the world's port /// - /// The for the operation + /// The for the operation /// A resulting in if the operation succeeded, otherwise Task ClosePort(CancellationToken cancellationToken); /// /// Causes the world to start listening on a /// + /// The port to change to /// The for the operation /// A resulting in if the operation succeeded, otherwise Task SetPort(ushort newPort, CancellationToken cancellatonToken); @@ -74,7 +76,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Attempts to change the current to /// /// The new - /// The for the operation + /// The for the operation /// A resulting in if the operation succeeded, otherwise Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken); } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs index 539181995f..3e8c3d51b4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public sealed class LaunchResult { /// - /// The time it took for to return + /// The time it took for to return /// public TimeSpan StartupTime { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs index 16c1319ba3..6ede61b0b6 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs @@ -12,7 +12,16 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public IDmbProvider Dmb { get; set; } + /// + /// Construct a + /// public ReattachInformation() { } + + /// + /// Construct a from a given and + /// + /// The to copy values from + /// The used to assign public ReattachInformation(Models.ReattachInformation copy, IDmbFactory dmbFactory) : base(copy) { Dmb = dmbFactory.FromCompileJob(copy.CompileJob); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index b03163fbc0..445ac39386 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -177,7 +177,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { logger.LogInformation("Monitor activation. Reason: {0}", activationReason); await Task.Yield(); - throw new NotImplementedException(); + throw new NotImplementedException(nameof(monitorState)); } /// @@ -224,7 +224,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } var chatTask = Task.CompletedTask; - using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { MonitorActivationReason activationReason = default; //multiple things may have happened, handle them one at a time @@ -276,7 +276,7 @@ namespace Tgstation.Server.Host.Components.Watchdog for (var retryAttempts = 1; state.NextAction == MonitorAction.Restart; ++retryAttempts) { WatchdogLaunchResult result; - using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) result = await LaunchNoLock(false, false, false, cancellationToken).ConfigureAwait(false); await chatTask.ConfigureAwait(false); @@ -320,7 +320,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { - using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { ActiveLaunchParameters = launchParameters; if (Running) @@ -462,21 +462,21 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task Launch(CancellationToken cancellationToken) { - using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) return await LaunchNoLock(true, true, false, cancellationToken).ConfigureAwait(false); } /// public async Task Restart(bool graceful, CancellationToken cancellationToken) { - using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) return await RestartNoLock(graceful, cancellationToken).ConfigureAwait(false); } /// public async Task Terminate(bool graceful, CancellationToken cancellationToken) { - using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) await TerminateNoLock(graceful, true, cancellationToken).ConfigureAwait(false); } @@ -499,7 +499,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) { - using (await SemaphoreContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { if (!Running) return; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 638f5f6940..2f666ad6be 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using System; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog @@ -36,7 +37,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Construct a /// - /// The value of /// The value of /// The value of /// The value of diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs index a43408e6b4..0dd94f8ba5 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs @@ -18,7 +18,16 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public ReattachInformation Bravo { get; set; } + /// + /// Construct a + /// public WatchdogReattachInformation() { } + + /// + /// Construct a from a given with a given + /// + /// The to copy information from + /// The used to build the s public WatchdogReattachInformation(Models.WatchdogReattachInformation copy, IDmbFactory dmbFactory): base(copy) { if (copy.Alpha != null) diff --git a/src/Tgstation.Server.Host/Core/SemaphoreContext.cs b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs similarity index 80% rename from src/Tgstation.Server.Host/Core/SemaphoreContext.cs rename to src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs index f2665250d8..81b5d76d67 100644 --- a/src/Tgstation.Server.Host/Core/SemaphoreContext.cs +++ b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Core /// /// Async lock context helper /// - public sealed class SemaphoreContext : IDisposable + public sealed class SemaphoreSlimContext : IDisposable { /// /// Asyncronously locks a @@ -15,13 +15,13 @@ namespace Tgstation.Server.Host.Core /// The to lock /// The for the operation /// A resulting in the for the lock - public static async Task Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken) + public static async Task Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken) { if (semaphore == null) throw new ArgumentNullException(nameof(semaphore)); await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - return new SemaphoreContext(semaphore); + return new SemaphoreSlimContext(semaphore); } /// @@ -38,12 +38,12 @@ namespace Tgstation.Server.Host.Core /// Construct a /// /// The value of - SemaphoreContext(SemaphoreSlim lockedSemaphore) => this.lockedSemaphore = lockedSemaphore; + SemaphoreSlimContext(SemaphoreSlim lockedSemaphore) => this.lockedSemaphore = lockedSemaphore; /// /// Finalize the /// - ~SemaphoreContext() => Dispose(); + ~SemaphoreSlimContext() => Dispose(); /// /// Release the lock on diff --git a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs index 6a1a550a42..af817a1825 100644 --- a/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamDaemonSettings.cs @@ -32,8 +32,13 @@ namespace Tgstation.Server.Host.Models public Instance Instance { get; set; } /// - /// See + /// See /// - public CompileJob CompileJob { get; set; } - } + public CompileJob ActiveCompileJob { get; set; } + + /// + /// See + /// + public CompileJob StagedCompileJob { get; set; } + } } diff --git a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs index defb570cd6..b0c8f7dcb7 100644 --- a/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs +++ b/src/Tgstation.Server.Host/Models/ReattachInformationBase.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Models /// /// Base class for /// - public class ReattachInformationBase + public abstract class ReattachInformationBase { /// /// Used to identify and authenticate the DreamDaemon instance @@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Models public int ProcessId { get; set; } /// - /// If the of is being used + /// If the of the associated dmb is being used /// public bool IsPrimary { get; set; } @@ -48,8 +48,15 @@ namespace Tgstation.Server.Host.Models [Required] public string ChatChannelsJson { get; set; } + /// + /// Construct a + /// public ReattachInformationBase() { } + /// + /// Construct a from a given + /// + /// The to copy values from protected ReattachInformationBase(ReattachInformationBase copy) { if (copy == null) diff --git a/src/Tgstation.Server.Host/Models/WatchdogReattachInformationBase.cs b/src/Tgstation.Server.Host/Models/WatchdogReattachInformationBase.cs index 2d578f2915..8cfa201257 100644 --- a/src/Tgstation.Server.Host/Models/WatchdogReattachInformationBase.cs +++ b/src/Tgstation.Server.Host/Models/WatchdogReattachInformationBase.cs @@ -2,14 +2,25 @@ namespace Tgstation.Server.Host.Models { - public class WatchdogReattachInformationBase + /// + /// Base class for + /// + public abstract class WatchdogReattachInformationBase { /// /// If the Alpha session is the active session /// public bool AlphaIsActive { get; set; } + /// + /// Construct a + /// public WatchdogReattachInformationBase() { } + + /// + /// Construct a from a given + /// + /// The to copy values from protected WatchdogReattachInformationBase(WatchdogReattachInformationBase copy) { AlphaIsActive = copy?.AlphaIsActive ?? throw new ArgumentNullException(nameof(copy)); From 7a86602b71b684c121f497e44661dc33e9fc8da3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Jul 2018 12:50:23 -0400 Subject: [PATCH 186/397] Move live and staged CompileJobs out of IWatchdog and back to db DmbFactory handles setting staged. Watchdog handles clearing it and setting active --- .../Components/DmbFactory.cs | 33 +++++++++++++--- .../Components/DreamMaker.cs | 4 +- .../Components/ICompileJobConsumer.cs | 4 +- .../Components/InstanceFactory.cs | 4 +- .../Components/Watchdog/IWatchdog.cs | 10 ----- .../Components/Watchdog/Watchdog.cs | 39 ++++++++++++++----- .../Components/Watchdog/WatchdogFactory.cs | 19 ++++++++- .../Controllers/DreamDaemonController.cs | 6 +-- .../Models/DatabaseContext.cs | 4 +- 9 files changed, 86 insertions(+), 37 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index 40d50d393a..5609c3f08b 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -36,6 +36,11 @@ namespace Tgstation.Server.Host.Components /// The for /// readonly CancellationTokenSource cleanupCts; + + /// + /// The the belongs to + /// + readonly long instanceId; /// /// representing calls to @@ -57,10 +62,12 @@ namespace Tgstation.Server.Host.Components /// /// The value of /// The value of - public DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager) + /// The used to populate + public DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager, Models.Instance instance) { this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + instanceId = instance?.Id ?? throw new ArgumentNullException(nameof(instance)); cleanupCts = new CancellationTokenSource(); jobLockCounts = new Dictionary(); @@ -92,12 +99,25 @@ namespace Tgstation.Server.Host.Components } /// - public void LoadCompileJob(CompileJob job) + public Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken) => LoadCompileJob(job, true, cancellationToken); + + async Task LoadCompileJob(CompileJob job, bool setAsStagedInDb, CancellationToken cancellationToken) { if (job == null) throw new ArgumentNullException(nameof(job)); if (!job.DMApiValidated || job.Job.Cancelled || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null) throw new InvalidOperationException("Cannot load incomplete compile job!"); + if (setAsStagedInDb) + await databaseContextFactory.UseContext(async db => + { + var ddsettings = new DreamDaemonSettings + { + InstanceId = instanceId + }; + db.DreamDaemonSettings.Attach(ddsettings); + ddsettings.StagedCompileJob = job; + await db.Save(cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); lock (this) { var oldDmbProvider = nextDmbProvider; @@ -130,13 +150,16 @@ namespace Tgstation.Server.Host.Components /// public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { - var cj = await db.CompileJobs.OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + //where complete clause not necessary, only successful COMPILEjobs get in the db + var cj = await db.Instances.Where(x => x.Id == instanceId).SelectMany(x => x.CompileJobs).OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (cj == default(CompileJob)) return; - LoadCompileJob(cj); + var directoriesTask = ioManager.GetDirectories(".", cancellationToken); + var compileJobTask = LoadCompileJob(cj, false, cancellationToken); //delete all other compile jobs - var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false); + var directories = await directoriesTask.ConfigureAwait(false); await Task.WhenAll(directories.Where(x => x != cj.Job.ToString()).Select(x => ioManager.DeleteDirectory(x, cancellationToken))).ConfigureAwait(false); + await compileJobTask.ConfigureAwait(false); }); /// diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index e51b301ef0..75ac5c271b 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -172,7 +172,7 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// The for the operation /// A representing the running operation - async Task ModifyDme(Host.Models.CompileJob job, CancellationToken cancellationToken) + async Task ModifyDme(Models.CompileJob job, CancellationToken cancellationToken) { var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); var dmePath = ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmeExtension)); @@ -288,7 +288,7 @@ namespace Tgstation.Server.Host.Components await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); await symATask.ConfigureAwait(false); } - compileJobConsumer.LoadCompileJob(job); + await compileJobConsumer.LoadCompileJob(job, cancellationToken).ConfigureAwait(false); return job; } catch diff --git a/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs index 2e96eb26a9..248590a01f 100644 --- a/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs +++ b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs @@ -1,11 +1,13 @@ using Microsoft.Extensions.Hosting; using System; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { interface ICompileJobConsumer : IHostedService, IDisposable { - void LoadCompileJob(CompileJob job); + Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 62112fac9b..7939caed1b 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Components } /// - public IInstance CreateInstance(Host.Models.Instance metadata) + public IInstance CreateInstance(Models.Instance metadata) { //Create the ioManager for the instance @@ -41,7 +41,7 @@ namespace Tgstation.Server.Host.Components var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); var codeModificationsIoMananger = new ResolvingIOManager(instanceIoManager, "CodeModifications"); - var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager); + var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, metadata); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 85e6594d9f..99e19e7ef0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -26,16 +26,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// LaunchResult LastLaunchResult { get; } - /// - /// The that is currently live - /// - Models.CompileJob LiveCompileJob { get; } - - /// - /// The that is staged to go live - /// - Models.CompileJob StagedCompileJob { get; } - /// /// The the active server is using /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 445ac39386..ef3688fe7a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -29,12 +29,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public LaunchResult LastLaunchResult { get; private set; } - /// - public Models.CompileJob LiveCompileJob { get; private set; } - - /// - public Models.CompileJob StagedCompileJob { get; private set; } - /// public DreamDaemonLaunchParameters ActiveLaunchParameters { get; private set; } @@ -69,11 +63,21 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IReattachInfoHandler reattachInfoHandler; + /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + /// /// The for the /// readonly SemaphoreSlim semaphore; + /// + /// The the belongs to + /// + readonly long instanceId; + CancellationTokenSource monitorCts; Task monitorTask; @@ -94,14 +98,18 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the /// The value of /// The value of + /// The value of /// The initial value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, DreamDaemonLaunchParameters initialLaunchParameters) + /// The containing the value of + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, DreamDaemonLaunchParameters initialLaunchParameters, Models.Instance instance) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + instanceId = instance?.Id ?? throw new ArgumentNullException(nameof(instance)); if (serverUpdater == null) throw new ArgumentNullException(nameof(serverUpdater)); @@ -416,11 +424,24 @@ namespace Tgstation.Server.Host.Components.Watchdog using (cancellationToken.Register(() => cancelTcs.SetCanceled())) await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false); + //update the live and staged jobs in the db + await databaseContextFactory.UseContext(async db => + { + var settings = new Models.DreamDaemonSettings + { + InstanceId = instanceId + }; + var cj = (AlphaIsActive ? alphaServer : bravoServer).Dmb.CompileJob; + db.CompileJobs.Attach(cj); + db.DreamDaemonSettings.Attach(settings); + settings.StagedCompileJob = null; + settings.ActiveCompileJob = cj; + await db.Save(cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); + //both servers are now running, alpha is the active server, huzzah AlphaIsActive = doReattach ? reattachInfo.AlphaIsActive : true; - LiveCompileJob = AlphaIsActive ? alphaServer.Dmb.CompileJob : bravoServer.Dmb.CompileJob; LastLaunchResult = alphaLrt.Result; - StagedCompileJob = null; Running = true; if (startMonitor) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 2f666ad6be..5df22cc735 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -34,6 +34,17 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IReattachInfoHandler reattachInfoHandler; + /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + + /// + /// The for the + /// + readonly Models.Instance instance; + + /// /// Construct a /// @@ -42,16 +53,20 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionManagerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler) + /// The value of + /// The value of + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionManagerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, Models.Instance instance) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters) => new Watchdog(chat, sessionManagerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, launchParameters); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters) => new Watchdog(chat, sessionManagerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, launchParameters, instance); } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index e69a18aca7..375e4dace8 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -83,7 +83,7 @@ namespace Tgstation.Server.Host.Controllers var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadMetadata) != 0; var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (int)DreamDaemonRights.ReadRevision) != 0; - var settings = metadata ? await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false) : null; + var settings = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); var result = new DreamDaemon(); if(metadata) { @@ -104,8 +104,8 @@ namespace Tgstation.Server.Host.Controllers }; if (revision) { - result.ActiveCompileJob = dd.LiveCompileJob?.ToApi(); - result.StagedCompileJob = dd.StagedCompileJob?.ToApi(); + result.ActiveCompileJob = settings.ActiveCompileJob?.ToApi(); + result.StagedCompileJob = settings.StagedCompileJob?.ToApi(); } return Json(result); diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 2229e1c9da..554118c0f4 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -57,9 +57,7 @@ namespace Tgstation.Server.Host.Models /// public DbSet ChatChannels { get; set; } - /// - /// The s in the - /// + /// public DbSet Jobs { get; set; } /// From bec7bead4276581f0337c47967f0571a3ae6d7d6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Jul 2018 12:52:47 -0400 Subject: [PATCH 187/397] Adds Watchdog to Instance Start/StopAsync --- src/Tgstation.Server.Host/Components/Instance.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 29f6b1e1eb..b97f722e0c 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -125,10 +125,10 @@ namespace Tgstation.Server.Host.Components } /// - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)); + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken)); /// - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken)); /// public async Task SetAutoUpdateInterval(int? newInterval) From ed1dfb4efbcdd57c2b1e36a2e8625f3d368e8252 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Jul 2018 12:56:20 -0400 Subject: [PATCH 188/397] Fix Watchdog.StartAsync not respectiong DreamDaemonSettings.AutoStart --- .../Components/Watchdog/IWatchdogFactory.cs | 4 ++-- .../Components/Watchdog/Watchdog.cs | 15 +++++++++++++-- .../Components/Watchdog/WatchdogFactory.cs | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs index 75407159ec..35326200d6 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs @@ -11,8 +11,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Creates a /// /// The for the with - /// The initial for the + /// The initial for the /// A new - IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters); + IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index ef3688fe7a..d2a32643e2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -78,6 +78,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly long instanceId; + /// + /// If the should in + /// + readonly bool autoStart; + CancellationTokenSource monitorCts; Task monitorTask; @@ -101,7 +106,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The initial value of /// The containing the value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, DreamDaemonLaunchParameters initialLaunchParameters, Models.Instance instance) + /// The value of + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, DreamDaemonLaunchParameters initialLaunchParameters, Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -110,6 +116,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); instanceId = instance?.Id ?? throw new ArgumentNullException(nameof(instance)); + this.autoStart = autoStart; if (serverUpdater == null) throw new ArgumentNullException(nameof(serverUpdater)); @@ -502,7 +509,11 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public Task StartAsync(CancellationToken cancellationToken) => LaunchNoLock(true, true, true, cancellationToken); + public async Task StartAsync(CancellationToken cancellationToken) + { + if (autoStart) + await LaunchNoLock(true, true, true, cancellationToken).ConfigureAwait(false); + } /// public async Task StopAsync(CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 5df22cc735..ad068c4b31 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -67,6 +67,6 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonLaunchParameters launchParameters) => new Watchdog(chat, sessionManagerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, launchParameters, instance); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionManagerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, settings, instance, settings.AutoStart.Value); } } From f3628593058e86b98e3c5685a0622c0d39911a20 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Jul 2018 13:43:09 -0400 Subject: [PATCH 189/397] DMAPI compile fixes --- src/DMAPI/tgs/v4/api.dm | 66 ++++++++++++++---------------------- src/DMAPI/tgs/v4/commands.dm | 4 +-- 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 01e46eb152..6c40ca9ce1 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -27,6 +27,7 @@ var/access_identifier var/instance_name var/host_path + var/json_path var/chat_channels_json_path var/chat_commands_json_path var/reboot_mode = TGS_REBOOT_MODE_NORMAL @@ -57,8 +58,8 @@ TGS_ERROR_LOG("Failed to decode info json: [json_file]") return - access_token = cached_json["access_token"] - instance_id = text2num(cached_json["instance_id"]) + access_identifier = cached_json["access_identifier"] + instance_name = text2num(cached_json["instance_name"]) host_path = cached_json["host_path"] if(cached_json["api_validate_only"]) Export(TGS4_COMM_VALIDATE) @@ -68,8 +69,23 @@ chat_commands_json_path = cached_json["chat_commands_json"] src.event_handler = event_handler instance_name = cached_json["instance_name"] - port_1 = world.port - port_2 = cached_json["next_port"] + + cached_test_merges = list() + var/json = cached_json["test_merges"] + for(var/I in test_merges) + var/datum/tgs_revision_information/test_merge/tm = new + tm.number = text2num(I) + var/list/entry = json[I] + tm.pull_request_commit = entry["pr_commit"] + tm.author = entry["author"] + tm.title = entry["title"] + tm.commit = entry["commit"] + tm.origin_commit = entry["origin_commit"] + tm.time_merged = text2num(entry["time_merged"]) + tm.comment = entry["comment"] + tm.url = entry["url"] + + ListCustomCommands() /datum/tgs_api/v4/OnInitializationComplete() @@ -86,7 +102,7 @@ if(!their_sCK) return FALSE //continue world/Topic - if(their_sCK != access_token) + if(their_sCK != access_identifier) return "Invalid comms key!"; var/command = params[TGS4_TOPIC_COMMAND] @@ -108,18 +124,6 @@ return "Unknown command: [command]" -/datum/tgs_api/v4/proc/SwapPorts(delayed) - set waitfor = FALSE - var/new_port = world.port == port_1 ? port_2 : port_1 - event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) - if(delayed) - world.OpenPort("none") //close the port - sleep(50) //wait for other server to close port - - //do NOT give up, if we remain unresponsive we will be killed - while(!world.OpenPort(new_port)) - sleep(10) - /datum/tgs_api/v4/proc/Export(command, list/data) var/url = "[host_path]/Interop?[TGS4_INTEROP_ACCESS_IDENTIFIER]=[url_encode(access_identifier)]&[TGS4_PARAMETER_COMMAND]=[url_encode(command)]" if(data) @@ -145,37 +149,19 @@ return content /datum/tgs_api/v4/OnReboot() - var/json = Export(TGS4_COMM_SERVER_REBOOT) + var/json = Export(TGS4_COMM_WORLD_REBOOT) var/list/result = json_decode(json) if(!result) return - if(json["port_swap"]) - SwapPorts(json["delayed"]) + + //TODO: Port handling /datum/tgs_api/v4/InstanceName() return instance_name /datum/tgs_api/v4/TestMerges() - if(cached_test_merges) - return cached_test_merges - - . = list() - cached_test_merges = . - var/json = cached_json["test_merges"] - for(var/I in json) - var/datum/tgs_revision_information/test_merge/tm = new - tm.number = text2num(I) - var/list/entry = json[I] - tm.pull_request_commit = entry["pr_commit"] - tm.author = entry["author"] - tm.title = entry["title"] - tm.commit = entry["commit"] - tm.origin_commit = entry["origin_commit"] - tm.time_merged = text2num(entry["time_merged"]) - tm.comment = entry["comment"] - tm.url = entry["url"] - . += tm - + return cached_test_merges + /datum/tgs_api/v4/EndProcess() Export(TGS4_COMM_END_PROCESS) diff --git a/src/DMAPI/tgs/v4/commands.dm b/src/DMAPI/tgs/v4/commands.dm index 962a497db6..352a53519e 100644 --- a/src/DMAPI/tgs/v4/commands.dm +++ b/src/DMAPI/tgs/v4/commands.dm @@ -7,7 +7,7 @@ if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) TGS_ERROR_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!") continue - + if(results[command_name]) var/datum/other = custom_commands[command_name] TGS_ERROR_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!") @@ -15,7 +15,7 @@ results[command_name] = list("help_text" = stc.help_text, "admin_only" = stc.admin_only) custom_commands[command_name] = stc - var/commands_file = chat_commands_json + var/commands_file = chat_commands_json_file if(!commands_file) return text2file(json_encode(results), commands_file) From 1d81be37fcbda441f5825b752296dc74c2054351 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 10 Jul 2018 13:55:03 -0400 Subject: [PATCH 190/397] More DMAPI fixes --- src/DMAPI/tgs/v4/api.dm | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 6c40ca9ce1..cce546d7c4 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -72,7 +72,7 @@ cached_test_merges = list() var/json = cached_json["test_merges"] - for(var/I in test_merges) + for(var/I in json) var/datum/tgs_revision_information/test_merge/tm = new tm.number = text2num(I) var/list/entry = json[I] @@ -85,6 +85,9 @@ tm.comment = entry["comment"] tm.url = entry["url"] + cached_revision = new + cached_revision.commit = cached_json["commit"] + cached_revision.origin_commit = cached_json["origin_commit"] ListCustomCommands() @@ -98,14 +101,14 @@ /datum/tgs_api/v4/OnTopic(T) var/list/params = params2list(T) - var/their_sCK = params[TGS4_TOPIC_TOKEN] + var/their_sCK = params[TGS4_INTEROP_ACCESS_IDENTIFIER] if(!their_sCK) return FALSE //continue world/Topic if(their_sCK != access_identifier) return "Invalid comms key!"; - var/command = params[TGS4_TOPIC_COMMAND] + var/command = params[TGS4_INTEROP_ACCESS_IDENTIFIER] if(!command) return "No command!" @@ -166,11 +169,6 @@ Export(TGS4_COMM_END_PROCESS) /datum/tgs_api/v4/Revision() - if(!cached_revision) - var/json = cached_json["revision"] - cached_revision = new - cached_revision.commit = json["commit"] - cached_revision.origin_commit = json["origin_commit"] return cached_revision /datum/tgs_api/v4/ChatBroadcast(message, list/channels) From 36cd4c5e89f1f86b3838f8899b22ce8f05296bca Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 11 Jul 2018 20:30:59 -0400 Subject: [PATCH 191/397] Swear i already fixed this --- src/DMAPI/tgs/v4/commands.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/v4/commands.dm b/src/DMAPI/tgs/v4/commands.dm index 352a53519e..6034affe19 100644 --- a/src/DMAPI/tgs/v4/commands.dm +++ b/src/DMAPI/tgs/v4/commands.dm @@ -15,7 +15,7 @@ results[command_name] = list("help_text" = stc.help_text, "admin_only" = stc.admin_only) custom_commands[command_name] = stc - var/commands_file = chat_commands_json_file + var/commands_file = chat_commands_json_path if(!commands_file) return text2file(json_encode(results), commands_file) From ce10cb80730d656ae1997f2177ed46b464ceadc0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:03:25 -0400 Subject: [PATCH 192/397] Properly use byondTopicSender sanitization --- .../Components/Watchdog/ISessionController.cs | 2 +- .../Components/Watchdog/Watchdog.cs | 14 +++++++++++--- .../Components/Watchdog/WatchdogFactory.cs | 14 +++++++++++--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs index 949e1b53d9..faa997cbf1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs @@ -52,7 +52,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Sends a command to DreamDaemon through /world/Topic() /// - /// The command to send + /// The sanitized command to send /// The for the operation /// A resulting in the result of /world/Topic() Task SendCommand(string command, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index d2a32643e2..52e2aa19cb 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Byond.TopicSender; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -68,6 +69,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IDatabaseContextFactory databaseContextFactory; + /// + /// The for the + /// + readonly IByondTopicSender byondTopicSender; + /// /// The for the /// @@ -104,10 +110,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of + /// The value of /// The initial value of /// The containing the value of /// The value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, DreamDaemonLaunchParameters initialLaunchParameters, Models.Instance instance, bool autoStart) + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, DreamDaemonLaunchParameters initialLaunchParameters, Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -115,6 +122,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); instanceId = instance?.Id ?? throw new ArgumentNullException(nameof(instance)); this.autoStart = autoStart; @@ -540,7 +548,7 @@ namespace Tgstation.Server.Host.Components.Watchdog foreach (var I in parameters) { builder.Append("&"); - builder.Append(I); + builder.Append(byondTopicSender.SanitizeString(I)); } var activeServer = AlphaIsActive ? alphaServer : bravoServer; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index ad068c4b31..e14419c9e0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Byond.TopicSender; +using Microsoft.Extensions.Logging; using System; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; @@ -39,6 +40,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IDatabaseContextFactory databaseContextFactory; + /// + /// The for the + /// + readonly IByondTopicSender byondTopicSender; + /// /// The for the /// @@ -54,8 +60,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of + /// The value of /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionManagerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, Models.Instance instance) + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionManagerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, Models.Instance instance) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); @@ -63,10 +70,11 @@ namespace Tgstation.Server.Host.Components.Watchdog this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionManagerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, settings, instance, settings.AutoStart.Value); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionManagerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, settings, instance, settings.AutoStart.Value); } } From d581019c6b94beae4d174930b657c601b79f8109 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:04:08 -0400 Subject: [PATCH 193/397] Removes unneccessary cast --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 52e2aa19cb..c5998416ca 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public DreamDaemonLaunchParameters LastLaunchParameters { get; private set; } /// - public RebootState? RebootState => Running ? (RebootState?)(AlphaIsActive ? alphaServer?.RebootState : bravoServer?.RebootState) : null; + public RebootState? RebootState => Running ? (AlphaIsActive ? alphaServer?.RebootState : bravoServer?.RebootState) : null; /// /// The for the From a3251cf4f77b39fd8a6b9f3de6fd8948922c6ff6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:12:58 -0400 Subject: [PATCH 194/397] Properly specify EventType enum values --- .../Components/EventType.cs | 64 +++++-------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs index 887d8ffe19..fa8b5a2b29 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -8,97 +8,65 @@ /// /// Parameters: Reference name, commit sha /// - RepoResetOrigin, + RepoResetOrigin = 0, /// /// Parameters: Reference name, commit sha /// - RepoCheckout, + RepoCheckout = 1, /// /// No parameters /// - RepoFetch, + RepoFetch = 2, /// /// Parameters: Comma separated list in form of "#{Pull Request Number} @ {7 character SHA} /// - RepoMergePullRequests, + RepoMergePullRequests = 3, /// /// Parameters: Current version, new version /// - ByondChangeStart, + ByondChangeStart = 4, /// /// No parameters /// - ByondChangeCancelled, + ByondChangeCancelled = 5, /// /// Parameters: Error string /// - ByondFail, + ByondFail = 6, /// /// No parameters /// - ByondStageComplete, + ByondStageComplete = 7, /// /// No parameters /// - ByondChangeComplete, + ByondChangeComplete = 8, /// /// Parameters: Commit sha, parameter of /// - CompileStart, + CompileStart = 9, /// /// No parameters /// - CompileCancelled, + CompileCancelled = 10, /// /// Parameters: Error string /// - CompileFailure, + CompileFailure = 11, /// /// No parameters /// - CompileComplete, - - /// - /// Parameters: Access token - /// - DDLaunched, + CompileComplete = 12, + /// /// Parameters: Exit code /// - DDCrash, + DDOtherCrash = 13, /// /// No parameters /// - DDExit, - /// - /// Parameters: Exit code - /// - DDOtherCrash, - /// - /// No parameters - /// - DDOtherExit, - /// - /// No parameters - /// - DDRestart, - /// - /// No parameters - /// - DDBeginGracefulRestart, - /// - /// No parameters - /// - DDBeginGracefulShutdown, - /// - /// No parameters - /// - DDCancelGraceful, - /// - /// No parameters - /// - DDTerminated, + DDOtherExit = 14, } } From f87c27704023886142b3172130d51b1d93a48854 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:13:37 -0400 Subject: [PATCH 195/397] Add more missing topic sanitization to SessionController --- .../Components/Watchdog/SessionController.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 43913c7e54..0792b0736c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -254,9 +254,18 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic(new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), String.Format(CultureInfo.InvariantCulture, "?{0}={1}&{2}={3}", InteropConstants.DMInteropAccessIdentifier, reattachInformation.AccessIdentifier, InteropConstants.DMParameterCommand, command), cancellationToken); + public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic( + new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), + String.Format(CultureInfo.InvariantCulture, + "?{0}={1}&{2}={3}", + byondTopicSender.SanitizeString(InteropConstants.DMInteropAccessIdentifier), + byondTopicSender.SanitizeString(reattachInformation.AccessIdentifier), + byondTopicSender.SanitizeString(InteropConstants.DMParameterCommand), + //intentionally don't sanitize command, that's up to the caller + command), + cancellationToken); - async Task SetPortImpl(ushort port, CancellationToken cancellationToken) => await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", InteropConstants.DMTopicChangePort, InteropConstants.DMParameterNewPort, port), cancellationToken).ConfigureAwait(false) == InteropConstants.DMResponseSuccess; + async Task SetPortImpl(ushort port, CancellationToken cancellationToken) => await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", byondTopicSender.SanitizeString(InteropConstants.DMTopicChangePort), byondTopicSender.SanitizeString(InteropConstants.DMParameterNewPort), byondTopicSender.SanitizeString(port.ToString(CultureInfo.InvariantCulture))), cancellationToken).ConfigureAwait(false) == InteropConstants.DMResponseSuccess; /// public async Task ClosePort(CancellationToken cancellationToken) From 649effdcc68ef7cf220f3e2d0b1d3799a9e13b0e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:14:28 -0400 Subject: [PATCH 196/397] Handle chat responses in Watchdog.HandleEvent --- .../Components/Watchdog/Watchdog.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index c5998416ca..8110f17efc 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -539,6 +540,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) { + string results; using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { if (!Running) @@ -552,8 +554,24 @@ namespace Tgstation.Server.Host.Components.Watchdog } var activeServer = AlphaIsActive ? alphaServer : bravoServer; - await activeServer.SendCommand(builder.ToString(), cancellationToken).ConfigureAwait(false); + results = await activeServer.SendCommand(builder.ToString(), cancellationToken).ConfigureAwait(false); } + + if (results == null) + return; + + List responses; + try + { + responses = JsonConvert.DeserializeObject>(results); + } + catch + { + logger.LogInformation("Recieved invalid response from DD when parsing event {0}:{1}{2}", eventType, Environment.NewLine, results); + return; + } + + await Task.WhenAll(responses.Select(x => chat.SendMessage(x.Message, x.ChannelIds, cancellationToken))).ConfigureAwait(false); } } } From 0c3aebce620d84b0de7da4cce9b1b473a36fc542 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:16:12 -0400 Subject: [PATCH 197/397] Document ChatResponse --- .../Components/Chat/ChatResponse.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs b/src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs index e7bc242642..67f8cf6782 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs @@ -2,9 +2,19 @@ namespace Tgstation.Server.Host.Components.Chat { + /// + /// Represents a chat message requested by DD + /// sealed class ChatResponse { + /// + /// The message string + /// public string Message { get; set; } + + /// + /// The list of internal channel ids to send to + /// public List ChannelIds { get; set; } } } From bf0e37058d763b163c742d8cb1655e95303c7ccb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:16:38 -0400 Subject: [PATCH 198/397] Change values of some builtin DMAPI events --- src/DMAPI/tgs.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index eee991a8aa..4e9749084b 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -44,8 +44,8 @@ //EVENT CODES -#define TGS_EVENT_PORT_SWAP 1 //before a port change is about to happen, extra parameter is new port -#define TGS_EVENT_REBOOT_MODE_CHANGE 2 //before a reboot mode change, extras parameters are the current and new reboot mode enums +#define TGS_EVENT_PORT_SWAP -2 //before a port change is about to happen, extra parameter is new port +#define TGS_EVENT_REBOOT_MODE_CHANGE -1 //before a reboot mode change, extras parameters are the current and new reboot mode enums //OTHER ENUMS From 1a9dfd11e9055c9508e2a04815cd92f03a58782f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:18:43 -0400 Subject: [PATCH 199/397] Add eventConsumer and logger fieds to DreamMaker --- .../Components/DreamMaker.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 75ac5c271b..c83a8e0e23 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -60,6 +61,14 @@ namespace Tgstation.Server.Host.Components /// The for /// readonly IApplication application; + /// + /// The for + /// + readonly IEventConsumer eventConsumer; + /// + /// The for + /// + readonly ILogger logger; /// /// Construct @@ -70,8 +79,9 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - /// - public DreamMaker(IByond byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application) + /// The value of + /// The value of + public DreamMaker(IByond byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger logger) { this.byond = byond; this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -79,6 +89,8 @@ namespace Tgstation.Server.Host.Components this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// From ff63c512f74b38ef01f619519fcf2bf7374d09e1 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:19:16 -0400 Subject: [PATCH 200/397] Remove unecessary namespace specifications --- src/Tgstation.Server.Host/Components/DreamMaker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index c83a8e0e23..b68260c224 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -131,10 +131,10 @@ namespace Tgstation.Server.Host.Components /// Compiles a .dme with DreamMaker /// /// The path to the DreamMaker executable - /// The for the operation + /// The for the operation /// The for the operation /// A representing the running operation - async Task RunDreamMaker(string dreamMakerPath, Host.Models.CompileJob job, CancellationToken cancellationToken) + async Task RunDreamMaker(string dreamMakerPath, Models.CompileJob job, CancellationToken cancellationToken) { using (var dm = new Process()) { @@ -181,7 +181,7 @@ namespace Tgstation.Server.Host.Components /// /// Adds server side includes to the .dme being compiled /// - /// The for the operation + /// The for the operation /// The for the operation /// A representing the running operation async Task ModifyDme(Models.CompileJob job, CancellationToken cancellationToken) From e5d8d461ded78b88ac0ec88a820ef6245258feef Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:27:28 -0400 Subject: [PATCH 201/397] Catch and log exceptions thrown by byondTopicSender --- .../Components/Watchdog/SessionController.cs | 45 +++++++++++++------ .../Watchdog/SessionControllerFactory.cs | 14 ++++-- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 0792b0736c..4d61522620 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -1,5 +1,6 @@ using Byond.TopicSender; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using System; using System.Collections.Generic; @@ -110,6 +111,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IChat chat; + /// + /// The for the + /// + readonly ILogger logger; + /// /// The waits on when DreamDaemon currently has it's ports closed /// @@ -147,15 +153,17 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The used to construct /// The value of /// The value of - public SessionController(ReattachInformation reattachInformation, ISession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, IChatJsonTrackingContext chatJsonTrackingContext, IChat chat) + /// The value of + public SessionController(ReattachInformation reattachInformation, ISession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, IChatJsonTrackingContext chatJsonTrackingContext, IChat chat, ILogger logger) { this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); - this.session = session ?? throw new ArgumentNullException(nameof(session)); - this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); if (interopRegistrar == null) throw new ArgumentNullException(nameof(interopRegistrar)); + this.session = session ?? throw new ArgumentNullException(nameof(session)); + this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); interopContext = interopRegistrar.Register(reattachInformation.AccessIdentifier, this); @@ -254,16 +262,27 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public Task SendCommand(string command, CancellationToken cancellationToken) => byondTopicSender.SendTopic( - new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), - String.Format(CultureInfo.InvariantCulture, - "?{0}={1}&{2}={3}", - byondTopicSender.SanitizeString(InteropConstants.DMInteropAccessIdentifier), - byondTopicSender.SanitizeString(reattachInformation.AccessIdentifier), - byondTopicSender.SanitizeString(InteropConstants.DMParameterCommand), - //intentionally don't sanitize command, that's up to the caller - command), - cancellationToken); + public async Task SendCommand(string command, CancellationToken cancellationToken) + { + try + { + return await byondTopicSender.SendTopic( + new IPEndPoint(IPAddress.Loopback, reattachInformation.Port), + String.Format(CultureInfo.InvariantCulture, + "?{0}={1}&{2}={3}", + byondTopicSender.SanitizeString(InteropConstants.DMInteropAccessIdentifier), + byondTopicSender.SanitizeString(reattachInformation.AccessIdentifier), + byondTopicSender.SanitizeString(InteropConstants.DMParameterCommand), + //intentionally don't sanitize command, that's up to the caller + command), + cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogInformation("Send command exception:{0}{1}", Environment.NewLine, e.Message); + return null; + } + } async Task SetPortImpl(ushort port, CancellationToken cancellationToken) => await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", byondTopicSender.SanitizeString(InteropConstants.DMTopicChangePort), byondTopicSender.SanitizeString(InteropConstants.DMParameterNewPort), byondTopicSender.SanitizeString(port.ToString(CultureInfo.InvariantCulture))), cancellationToken).ConfigureAwait(false) == InteropConstants.DMResponseSuccess; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 19de65758f..18be1f76af 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -1,4 +1,5 @@ using Byond.TopicSender; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Globalization; @@ -61,6 +62,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IChat chat; + /// + /// The for the + /// + readonly ILoggerFactory loggerFactory; + /// /// Construct a /// @@ -73,7 +79,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public SessionControllerFactory(IExecutor executor, IByond byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IInstance instance, IIOManager ioManager, IChat chat) + /// The value of + public SessionControllerFactory(IExecutor executor, IByond byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IInstance instance, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory) { this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); @@ -84,6 +91,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } /// @@ -149,7 +157,7 @@ namespace Tgstation.Server.Host.Components.Watchdog IsPrimary = primaryDirectory, Port = portToUse.Value, ProcessId = session.ProcessId - }, session, byondTopicSender, interopRegistrar, chatJsonTrackingContext, chat); + }, session, byondTopicSender, interopRegistrar, chatJsonTrackingContext, chat, loggerFactory.CreateLogger()); } catch { @@ -186,7 +194,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var session = executor.AttachToDreamDaemon(reattachInformation.ProcessId, byondLock); try { - return new SessionController(reattachInformation, session, byondTopicSender, interopRegistrar, chatJsonTrackingContext, chat); + return new SessionController(reattachInformation, session, byondTopicSender, interopRegistrar, chatJsonTrackingContext, chat, loggerFactory.CreateLogger()); } catch { From 70ab0e5b50c94c30cf84a0626a11c35989fd5bf7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:28:58 -0400 Subject: [PATCH 202/397] sessionManagerFactory -> sessionControllerFactory --- .../Components/Watchdog/WatchdogFactory.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index e14419c9e0..6c948960d0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The for the /// - readonly ISessionControllerFactory sessionManagerFactory; + readonly ISessionControllerFactory sessionControllerFactory; /// /// The for the @@ -55,17 +55,17 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Construct a /// /// The value of - /// The value of + /// The value of /// The value of /// The value of /// The value of /// The value of /// The value of /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionManagerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, Models.Instance instance) + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, Models.Instance instance) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); - this.sessionManagerFactory = sessionManagerFactory ?? throw new ArgumentNullException(nameof(sessionManagerFactory)); + this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); @@ -75,6 +75,6 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionManagerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, settings, instance, settings.AutoStart.Value); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, settings, instance, settings.AutoStart.Value); } } From 3024d632ea788e3bb03b8336442a5ae6ba003dac Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:41:51 -0400 Subject: [PATCH 203/397] Add sanitization to DD launch parameters --- .../Components/Watchdog/SessionControllerFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 18be1f76af..b5ea4e60f0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -144,8 +144,8 @@ namespace Tgstation.Server.Host.Components.Watchdog var byondLock = byond.UseExecutables(dmbProvider.CompileJob.ByondVersion); try { - var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", application.Version, interopJsonFile, InteropConstants.DMParamHostVersion, InteropConstants.DMParamInfoJson); - + //more sanitization here cause it uses the same scheme + var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(InteropConstants.DMParamHostVersion), byondTopicSender.SanitizeString(InteropConstants.DMParamInfoJson)); var session = executor.RunDreamDaemon(launchParameters, byondLock, dmbProvider, parameters, !primaryPort, !primaryDirectory); try From 5a9a9cb42245534355c0411d812695b76811d688 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:47:40 -0400 Subject: [PATCH 204/397] Add passing through active byond lock from DreamMaker --- src/Tgstation.Server.Host/Components/DreamMaker.cs | 7 ++++--- .../Components/Watchdog/ISessionControllerFactory.cs | 3 ++- .../Components/Watchdog/SessionControllerFactory.cs | 7 ++++--- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index b68260c224..1816ad5b00 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -98,9 +98,10 @@ namespace Tgstation.Server.Host.Components /// /// The timeout in seconds for validation /// The for the operation + /// The current /// The for the operation /// A resulting in if the DMAPI was successfully validated, otherwise - async Task VerifyApi(int timeout, Models.CompileJob job, CancellationToken cancellationToken) + async Task VerifyApi(int timeout, Models.CompileJob job, IByondExecutableLock byondLock, CancellationToken cancellationToken) { var launchParameters = new DreamDaemonLaunchParameters { @@ -114,7 +115,7 @@ namespace Tgstation.Server.Host.Components var provider = new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))); var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout); - using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, true, true, true, cancellationToken).ConfigureAwait(false)) + using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false)) { var timeoutTask = Task.Delay(timeoutAt - DateTimeOffset.Now, cancellationToken); @@ -278,7 +279,7 @@ namespace Tgstation.Server.Host.Components Status = CompilerStatus.Verifying; - ddVerified = job.ExitCode == 0 && await VerifyApi(apiValidateTimeout, job, cancellationToken).ConfigureAwait(false); + ddVerified = job.ExitCode == 0 && await VerifyApi(apiValidateTimeout, job, byondLock, cancellationToken).ConfigureAwait(false); } if (!ddVerified) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs index 32faa37b0e..9f307f44c0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs @@ -14,12 +14,13 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The to use /// The to use + /// The current if any /// If the of should be used /// If the of should be used /// If the should only validate the DMAPI then exit /// The for the operation /// A resulting in a new - Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken); + Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken); /// /// Create a from an existing DreamDaemon instance diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index b5ea4e60f0..4f2dc78885 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public async Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken) + public async Task LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken) { var portToUse = primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort; if (!portToUse.HasValue) @@ -141,7 +141,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false); try { - var byondLock = byond.UseExecutables(dmbProvider.CompileJob.ByondVersion); + var byondLock = currentByondLock ?? byond.UseExecutables(dmbProvider.CompileJob.ByondVersion); try { //more sanitization here cause it uses the same scheme @@ -167,7 +167,8 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch { - byondLock.Dispose(); + if (currentByondLock == null) + byondLock.Dispose(); throw; } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 8110f17efc..d41b375a8a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -390,14 +390,14 @@ namespace Tgstation.Server.Host.Components.Watchdog try { if (!doReattach || reattachInfo.Alpha == null) - alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, true, true, false, alphaStartCts.Token); + alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, alphaStartCts.Token); else alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken); //do a few seconds of delay so that any backends the servers use know that alpha came first await Task.Delay(AlphaBravoStartupSeperationInterval, cancellationToken).ConfigureAwait(false); Task bravoServerTask; if (!doReattach || reattachInfo.Bravo == null) - bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, false, false, false, cancellationToken); + bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, false, false, false, cancellationToken); else bravoServerTask = sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken); From f9f142bfb8150d2dc0026e6394bc1e72fe2f5912 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:49:03 -0400 Subject: [PATCH 205/397] Throw an aggregate exception if both alpha and bravo servers fail to start --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index d41b375a8a..add29a5cf6 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -404,7 +404,7 @@ namespace Tgstation.Server.Host.Components.Watchdog bravoServer = await bravoServerTask.ConfigureAwait(false); alphaServer = await alphaServerTask.ConfigureAwait(false); } - catch + catch (Exception e) { if (alphaServerTask != null) if (alphaServerTask.Status == TaskStatus.RanToCompletion) @@ -416,7 +416,10 @@ namespace Tgstation.Server.Host.Components.Watchdog { alphaServer = await alphaServerTask.ConfigureAwait(false); } - catch { } + catch (Exception e2) + { + throw new AggregateException(e, e2); + } } throw; } From 3fa9ed0b71edc79dba10a6f7b27804e4efd9ba05 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 11:58:59 -0400 Subject: [PATCH 206/397] Add compiler logging and events --- .../Components/DreamMaker.cs | 17 +++++++++++++---- .../Components/EventType.cs | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 1816ad5b00..aaeb1204e3 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -224,6 +224,8 @@ namespace Tgstation.Server.Host.Components /// public async Task Compile(string projectName, int apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) { + logger.LogTrace("Begin Compile"); + await eventConsumer.HandleEvent(EventType.CompileStart, new List{ repository.Origin }, cancellationToken).ConfigureAwait(false); try { Status = CompilerStatus.Copying; @@ -284,13 +286,13 @@ namespace Tgstation.Server.Host.Components if (!ddVerified) //server never validated or compile failed - await CleanupFailedCompile().ConfigureAwait(false); + await Task.WhenAll(CleanupFailedCompile(), eventConsumer.HandleEvent(EventType.CompileFailure, new List { job.ExitCode == 0 ? "1" : "0" }, cancellationToken)).ConfigureAwait(false); else { job.DMApiValidated = true; Status = CompilerStatus.Duplicating; - + //duplicate the dmb et al await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); @@ -298,8 +300,10 @@ namespace Tgstation.Server.Host.Components //symlink in the static data var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); - await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); - await symATask.ConfigureAwait(false); + var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken); + + await Task.WhenAll(symATask, symBTask).ConfigureAwait(false); + await eventConsumer.HandleEvent(EventType.CompileComplete, null, cancellationToken).ConfigureAwait(false); } await compileJobConsumer.LoadCompileJob(job, cancellationToken).ConfigureAwait(false); return job; @@ -310,6 +314,11 @@ namespace Tgstation.Server.Host.Components throw; } } + catch (OperationCanceledException) + { + await eventConsumer.HandleEvent(EventType.CompileCancelled, null, default).ConfigureAwait(false); + throw; + } finally { Status = CompilerStatus.Idle; diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs index fa8b5a2b29..53e6c9c9bd 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -44,7 +44,7 @@ ByondChangeComplete = 8, /// - /// Parameters: Commit sha, parameter of + /// Parameters: Origin commit sha /// CompileStart = 9, /// @@ -52,7 +52,7 @@ /// CompileCancelled = 10, /// - /// Parameters: Error string + /// Parameters: "1" if compile succeeded and api validation failed, "0" otherwise /// CompileFailure = 11, /// From 15dd20c76eda29017bf072ee98d0f306730a8fa0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 12:00:46 -0400 Subject: [PATCH 207/397] Use Task.WhenAll in Watchdog.LaunchNoLock instead of weird exception handling Remove uneeded verbosity on var declaration --- .../Components/Watchdog/Watchdog.cs | 52 +++++-------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index add29a5cf6..c0f07beaa5 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -363,7 +363,6 @@ namespace Tgstation.Server.Host.Components.Watchdog return null; } - Task chatTask; //this is necessary, the monitor could be in it's sleep loop trying to restart if (startMonitor && await StopMonitor().ConfigureAwait(false)) @@ -380,49 +379,26 @@ namespace Tgstation.Server.Host.Components.Watchdog if (alphaServer != null || bravoServer != null) throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!"); - WatchdogReattachInformation reattachInfo = doReattach ? await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false) : null; + var reattachInfo = doReattach ? await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false) : null; var doesntNeedNewDmb = doReattach && reattachInfo.Alpha != null && reattachInfo.Bravo != null; var dmbToUse = doesntNeedNewDmb ? null : await dmbFactory.LockNextDmb(cancellationToken).ConfigureAwait(false); Task alphaServerTask = null; try { - try - { - if (!doReattach || reattachInfo.Alpha == null) - alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, alphaStartCts.Token); - else - alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken); - //do a few seconds of delay so that any backends the servers use know that alpha came first - await Task.Delay(AlphaBravoStartupSeperationInterval, cancellationToken).ConfigureAwait(false); - Task bravoServerTask; - if (!doReattach || reattachInfo.Bravo == null) - bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, false, false, false, cancellationToken); - else - bravoServerTask = sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken); + if (!doReattach || reattachInfo.Alpha == null) + alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, alphaStartCts.Token); + else + alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken); + //do a few seconds of delay so that any backends the servers use know that alpha came first + await Task.Delay(AlphaBravoStartupSeperationInterval, cancellationToken).ConfigureAwait(false); + Task bravoServerTask; + if (!doReattach || reattachInfo.Bravo == null) + bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, false, false, false, cancellationToken); + else + bravoServerTask = sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken); - bravoServer = await bravoServerTask.ConfigureAwait(false); - alphaServer = await alphaServerTask.ConfigureAwait(false); - } - catch (Exception e) - { - if (alphaServerTask != null) - if (alphaServerTask.Status == TaskStatus.RanToCompletion) - alphaServer = await alphaServerTask.ConfigureAwait(false); - else - { - alphaStartCts.Cancel(); - try - { - alphaServer = await alphaServerTask.ConfigureAwait(false); - } - catch (Exception e2) - { - throw new AggregateException(e, e2); - } - } - throw; - } + await Task.WhenAll(alphaServerTask, bravoServerTask).ConfigureAwait(false); async Task CheckLaunch(ISessionController controller, string serverName) { @@ -478,7 +454,7 @@ namespace Tgstation.Server.Host.Components.Watchdog catch { if (alphaServer == null && bravoServer == null) - dmbToUse.Dispose(); //guaranteed to not be null here + dmbToUse.Dispose(); //guaranteed to not be null here DisposeAndNullControllers(); throw; } From 598d08b374d3f40696d2935b0ad8d355c4318f7c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 12:02:18 -0400 Subject: [PATCH 208/397] Add skeleton chat implementation --- .../Components/Chat/Chat.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Chat.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs new file mode 100644 index 0000000000..1b5a0fdf09 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Components.Chat +{ + /// + sealed class Chat : IChat + { + /// + public bool IrcConnected => throw new System.NotImplementedException(); + + /// + public bool DiscordConnected => throw new System.NotImplementedException(); + + /// + public Task ChangeChannels(IEnumerable newChannels, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + /// + public Task ChangeSettings(Api.Models.Internal.ChatSettings newSettings, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + /// + public Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + /// + public Task SendWatchdogMessage(string message, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + /// + public Task StartAsync(CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + /// + public Task StopAsync(CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + /// + public Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + } +} From cf2429f82e9c7fe9fb960d9a8a99d6b96d282871 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 12:06:21 -0400 Subject: [PATCH 209/397] Add Discord.Net nuget package --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 8dfd920fb5..9c8124a0ee 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -34,6 +34,7 @@ + @@ -60,4 +61,8 @@ + + + + From 63f9b2c2a566c4a6d56b42c4b742460ba6676ffd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 12:13:06 -0400 Subject: [PATCH 210/397] Update several dependencies --- .../Tgstation.Server.Host.csproj | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 9c8124a0ee..6aa781f294 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -36,18 +36,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + From a58aa5877d08e321f162bece3b6f89bbb44e8f5d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 12:51:03 -0400 Subject: [PATCH 211/397] Remove Octokit from Tgstation.Server.Host.Watchdog dependencies --- .../Tgstation.Server.Host.Watchdog.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index 157f22838f..70e1a609ed 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -18,7 +18,6 @@ - From 805d63c6d4302c589a3a0fdea07ce4ce62114015 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 12:52:09 -0400 Subject: [PATCH 212/397] Fix HttpStatusCode casts --- .../Controllers/DreamDaemonController.cs | 4 ++-- src/Tgstation.Server.Host/Controllers/DreamMakerController.cs | 2 +- src/Tgstation.Server.Host/Controllers/InstanceController.cs | 2 +- src/Tgstation.Server.Host/Controllers/JobController.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 375e4dace8..168d3a46da 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -52,7 +52,7 @@ namespace Tgstation.Server.Host.Controllers var instance = instanceManager.GetInstance(Instance); if (instance.Watchdog.Running) - return StatusCode(HttpStatusCode.Gone); + return StatusCode((int)HttpStatusCode.Gone); await jobManager.RegisterOperation(new Models.Job { @@ -118,7 +118,7 @@ namespace Tgstation.Server.Host.Controllers var instance = instanceManager.GetInstance(Instance); if (!instance.Watchdog.Running) - return StatusCode(HttpStatusCode.Gone); + return StatusCode((int)HttpStatusCode.Gone); await instance.Watchdog.Terminate(false, cancellationToken).ConfigureAwait(false); return Ok(); diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 3e5844e7ef..b936a07f87 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -80,7 +80,7 @@ namespace Tgstation.Server.Host.Controllers //alias for cancelling the latest job var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Select(x => new Job { Id = x.Job.Id, StoppedAt = x.Job.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); if (job.StoppedAt != null) - return StatusCode(HttpStatusCode.Gone); + return StatusCode((int)HttpStatusCode.Gone); await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 3bb4ab83c5..d5665f6a4b 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -108,7 +108,7 @@ namespace Tgstation.Server.Host.Controllers { var originalModel = await DatabaseContext.Instances.Where(x => x.Id == model.Id).FirstAsync(cancellationToken).ConfigureAwait(false); if (originalModel == default(Models.Instance)) - return StatusCode(HttpStatusCode.Gone); + return StatusCode((int)HttpStatusCode.Gone); throw new NotImplementedException(); } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 1dcab2f526..a84d50c8f5 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); if(job.StoppedAt != null) - return StatusCode(HttpStatusCode.Gone); + return StatusCode((int)HttpStatusCode.Gone); await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); From d7b8689576640eebcb3575f52df5698e3c218488 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 15:58:14 -0400 Subject: [PATCH 213/397] Work on implementing chat --- .../Models/ChatChannel.cs | 5 + .../Models/ChatProvider.cs | 17 ++ .../Models/ChatSettings.cs | 14 +- .../Models/Internal/ChatSettings.cs | 43 ++--- .../Rights/ChatSettingsRights.cs | 28 +-- .../Components/Chat/Channel.cs | 14 ++ .../Components/Chat/ChannelMapping.cs | 9 + .../Components/Chat/Chat.cs | 162 +++++++++++++++--- .../Components/Chat/IChat.cs | 22 ++- ...kingContext.cs => IJsonTrackingContext.cs} | 2 +- .../Components/Chat/IProviderFactory.cs | 18 ++ .../Components/Chat/Message.cs | 8 + .../Components/Chat/ProviderFactory.cs | 27 +++ .../Components/Chat/Providers/IProvider.cs | 52 ++++++ .../Chat/{ChatResponse.cs => Response.cs} | 2 +- .../Components/Chat/User.cs | 10 ++ .../Components/Watchdog/SessionController.cs | 6 +- .../Components/Watchdog/Watchdog.cs | 4 +- .../Models/ChatChannel.cs | 2 +- .../Models/ChatSettings.cs | 7 +- 20 files changed, 355 insertions(+), 97 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/ChatProvider.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/Channel.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs rename src/Tgstation.Server.Host/Components/Chat/{IChatJsonTrackingContext.cs => IJsonTrackingContext.cs} (73%) create mode 100644 src/Tgstation.Server.Host/Components/Chat/IProviderFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/Message.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs rename src/Tgstation.Server.Host/Components/Chat/{ChatResponse.cs => Response.cs} (93%) create mode 100644 src/Tgstation.Server.Host/Components/Chat/User.cs diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index a927090029..c7ab3b9b32 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -19,5 +19,10 @@ /// If the is an admin channel /// public bool IsAdminChannel { get; set; } + + /// + /// If the is a watchdog channel + /// + public bool IsWatchdogChannel { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/ChatProvider.cs b/src/Tgstation.Server.Api/Models/ChatProvider.cs new file mode 100644 index 0000000000..f41d9de9c5 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/ChatProvider.cs @@ -0,0 +1,17 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a chat service provider + /// + public enum ChatProvider + { + /// + /// Internet relay chat + /// + Irc, + /// + /// Superior chat service + /// + Discord + } +} diff --git a/src/Tgstation.Server.Api/Models/ChatSettings.cs b/src/Tgstation.Server.Api/Models/ChatSettings.cs index 2900a7e055..aab5431e9a 100644 --- a/src/Tgstation.Server.Api/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/ChatSettings.cs @@ -6,22 +6,10 @@ namespace Tgstation.Server.Api.Models /// public sealed class ChatSettings : Internal.ChatSettings { - /// - /// If the IRC connection is established - /// - [Permissions(DenyWrite = true)] - bool IrcConnected { get; set; } - - /// - /// If the Discord connection is established - /// - [Permissions(DenyWrite = true)] - bool DiscordConnected { get; set; } - /// /// Channels the Discord bot should listen/announce in /// - [Permissions(WriteRight = ChatSettingsRights.SetChannels)] + [Permissions(WriteRight = ChatSettingsRights.WriteChannels)] public List Channels { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs index a672895d3c..3357c507b1 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal @@ -8,44 +6,39 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Manage the server chat bots /// - [Model(RightsType.ChatSettings, RequiresInstance = true)] + [Model(RightsType.ChatSettings, RequiresInstance = true, CanCrud = true, ReadRight = ChatSettingsRights.Read)] public class ChatSettings { /// - /// If the IRC client is enabled + /// The settings id /// - [Permissions(WriteRight = ChatSettingsRights.SetIrcEnabled)] - public bool IrcEnabled { get; set; } + [Permissions(DenyWrite = true)] + public long Id { get; set; } /// - /// The IRC server name + /// The name of the connection /// - [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] + [Permissions(WriteRight = ChatSettingsRights.WriteName)] [Required] - public string IrcHost { get; set; } + public string Name { get; set; } /// - /// The IRC server port + /// If the connection is enabled /// - [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] - public ushort IrcPort { get; set; } + [Permissions(WriteRight = ChatSettingsRights.WriteEnabled)] + public bool Enabled { get; set; } /// - /// The IRC server NickServ password + /// The used for the connection /// - [Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)] - public string IrcNickServPassword { get; set; } + [Permissions(WriteRight = ChatSettingsRights.WriteProvider)] + public ChatProvider Provider { get; set; } /// - /// If the Discord bot is enabled + /// The information used to connect to the /// - [Permissions(WriteRight = ChatSettingsRights.SetDiscordEnabled)] - public bool DiscordEnabled { get; set; } - - /// - /// The Discord bot token - /// - [Permissions(ReadRight = ChatSettingsRights.SetDiscordSettings, WriteRight = ChatSettingsRights.SetDiscordSettings)] - public string DiscordBotToken { get; set; } + [Permissions(ReadRight = ChatSettingsRights.ReadConnectionString, WriteRight = ChatSettingsRights.ReadConnectionString)] + [Required] + public string ConnectionString { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs index d27723c28c..e8803311ac 100644 --- a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs @@ -13,24 +13,32 @@ namespace Tgstation.Server.Api.Rights /// None = 0, /// - /// User can enable/disable the IRC client + /// User can change /// - SetIrcEnabled = 1, + WriteEnabled = 1, /// - /// User can change the IRC settings + /// User can change /// - SetIrcSettings = 2, + WriteProvider = 2, /// - /// User can change the chat channels + /// User can change /// - SetChannels = 4, + WriteChannels = 4, /// - /// User can enable/disable the Discord bot + /// User can change /// - SetDiscordEnabled = 8, + WriteConnectionString = 8, /// - /// User can change the Discord settings + /// User can read /// - SetDiscordSettings = 16, + ReadConnectionString = 16, + /// + /// User can read all chat settings except + /// + Read = 32, + /// + /// User can change + /// + WriteName = 32 } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Channel.cs b/src/Tgstation.Server.Host/Components/Chat/Channel.cs new file mode 100644 index 0000000000..d9916a1014 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Channel.cs @@ -0,0 +1,14 @@ +namespace Tgstation.Server.Host.Components.Chat +{ + sealed class Channel + { + public long Id { get; set; } + + public string FriendlyName { get; set; } + + public string ConnectionName { get; set; } + + public bool IsAdminChannel { get; set; } + public bool IsPrivateChannel { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs new file mode 100644 index 0000000000..cdf098287a --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs @@ -0,0 +1,9 @@ +namespace Tgstation.Server.Host.Components.Chat +{ + sealed class ChannelMapping + { + public long ProviderId { get; set; } + public long ProviderChannelId { get; set; } + public bool IsWatchdogChannel { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 1b5a0fdf09..86cc30b99c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -1,59 +1,175 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat.Providers; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Chat { /// sealed class Chat : IChat { - /// - public bool IrcConnected => throw new System.NotImplementedException(); + /// + /// The for the + /// + readonly IProviderFactory providerFactory; - /// - public bool DiscordConnected => throw new System.NotImplementedException(); + /// + /// The for the + /// + readonly IIOManager ioManager; - /// - public Task ChangeChannels(IEnumerable newChannels, CancellationToken cancellationToken) + /// + /// Map of s in use, keyed by + /// + readonly Dictionary providers; + + /// + /// Map of s to s + /// + readonly Dictionary mappedChannels; + + /// + /// Used for remapping s + /// + long channelIdCounter; + + /// + /// Construct a + /// + /// The value of + /// The value of + public Chat(IProviderFactory providerFactory, IIOManager ioManager) { - throw new System.NotImplementedException(); + this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + + providers = new Dictionary(); + mappedChannels = new Dictionary(); + channelIdCounter = 1; } /// - public Task ChangeSettings(Api.Models.Internal.ChatSettings newSettings, CancellationToken cancellationToken) + public void Dispose() { - throw new System.NotImplementedException(); + foreach (var I in providers) + I.Value.Dispose(); + } + + /// + public async Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken) + { + if (newChannels == null) + throw new ArgumentNullException(nameof(newChannels)); + IProvider provider; + lock (providers) + if (!providers.TryGetValue(connectionId, out provider)) + return; + var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false); + if (results == null) //aborted + return; + var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping + { + IsWatchdogChannel = x.IsWatchdogChannel, + ProviderChannelId = y.Id, + ProviderId = connectionId + }); + + long baseId; + lock (this) + { + baseId = channelIdCounter; + channelIdCounter += results.Count; + } + lock (mappedChannels) + { + lock (providers) + if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) //aborted again + return; + foreach (var I in mappings) + mappedChannels.Add(baseId++, I); + } + } + + /// + public async Task ChangeSettings(ChatSettings newSettings, CancellationToken cancellationToken) + { + if (newSettings == null) + throw new ArgumentNullException(nameof(newSettings)); + IProvider provider; + lock (providers) + { + //raw settings changes forces a rebuild of the provider + if (providers.TryGetValue(newSettings.Id, out provider)) + { + providers.Remove(newSettings.Id); + provider.Dispose(); + } + if (newSettings.Enabled) + { + provider = providerFactory.CreateProvider(newSettings); + providers.Add(newSettings.Id, provider); + } + } + lock (mappedChannels) + foreach (var channelId in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key)) + mappedChannels.Remove(channelId); + if (newSettings.Enabled) + await provider.Connect(cancellationToken).ConfigureAwait(false); } /// public Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken) { - throw new System.NotImplementedException(); + if (message == null) + throw new ArgumentNullException(nameof(message)); + if (channelIds == null) + throw new ArgumentNullException(nameof(channelIds)); + + return Task.WhenAll(channelIds.Select(x => + { + ChannelMapping channelMapping; + lock(mappedChannels) + if (!mappedChannels.TryGetValue(x, out channelMapping)) + return Task.CompletedTask; + IProvider provider; + lock (providers) + if (!providers.TryGetValue(channelMapping.ProviderId, out provider)) + return Task.CompletedTask; + return provider.SendMessage(channelMapping.ProviderChannelId, message, cancellationToken); + })); } /// public Task SendWatchdogMessage(string message, CancellationToken cancellationToken) { - throw new System.NotImplementedException(); + List wdChannels; + 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); } /// - public Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))); + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + public Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken) { - throw new System.NotImplementedException(); + ioManager.ResolvePath("."); + throw new NotImplementedException(); } /// - public Task StopAsync(CancellationToken cancellationToken) + public bool Connected(long connectionId) { - throw new System.NotImplementedException(); - } - - /// - public Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken) - { - throw new System.NotImplementedException(); + lock (providers) + return providers.TryGetValue(connectionId, out var provider) && provider.Connected; } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/IChat.cs b/src/Tgstation.Server.Host/Components/Chat/IChat.cs index f73805eb2b..26eacbfd4f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChat.cs @@ -10,33 +10,31 @@ namespace Tgstation.Server.Host.Components.Chat /// /// For managing connected chat services /// - public interface IChat : IHostedService + public interface IChat : IHostedService, IDisposable { /// - /// If the IRC client is connected + /// If a given set of is connected /// - bool IrcConnected { get; } + /// The of the connection + /// if it is connected, otherwise + bool Connected(long connectionId); /// - /// If the Discord client is connected - /// - bool DiscordConnected { get; } - - /// - /// Change chat settings + /// Change chat settings. If the is not currently in use, a new connection will be made instead /// /// The new /// The for the operation - /// A representing the running operation + /// A representing the running operation. Will complete immediately if the property of is Task ChangeSettings(ChatSettings newSettings, CancellationToken cancellationToken); /// /// Change chat channels /// + /// The of the connection /// An of the new list of s /// The for the operation /// A representing the running operation - Task ChangeChannels(IEnumerable newChannels, CancellationToken cancellationToken); + Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken); /// /// Send a chat to a given set of @@ -63,6 +61,6 @@ namespace Tgstation.Server.Host.Components.Chat /// The name of the chat commands json /// The for the operation /// A resulting in a tied to the lifetime of the json trackings - Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken); + Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs similarity index 73% rename from src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs rename to src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs index 8926b33291..5988619602 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatJsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components.Chat /// /// Represents a tracking of dynamic chat json files /// - public interface IChatJsonTrackingContext : IDisposable + public interface IJsonTrackingContext : IDisposable { } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/IProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/IProviderFactory.cs new file mode 100644 index 0000000000..b4b47f2d5b --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/IProviderFactory.cs @@ -0,0 +1,18 @@ +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat.Providers; + +namespace Tgstation.Server.Host.Components.Chat +{ + /// + /// Factory for s + /// + interface IProviderFactory + { + /// + /// Create a + /// + /// The for the new provider + /// A new + IProvider CreateProvider(ChatSettings settings); + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs new file mode 100644 index 0000000000..e9b90e6560 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs @@ -0,0 +1,8 @@ +namespace Tgstation.Server.Host.Components.Chat.Providers +{ + sealed class Message + { + string Content { get; set; } + User User { get; set; } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs new file mode 100644 index 0000000000..b16cd3617c --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs @@ -0,0 +1,27 @@ +using System; +using System.Globalization; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Components.Chat.Providers; + +namespace Tgstation.Server.Host.Components.Chat +{ + /// + sealed class ProviderFactory : IProviderFactory + { + /// + public IProvider CreateProvider(Api.Models.Internal.ChatSettings settings) + { + if (settings == null) + throw new ArgumentNullException(nameof(settings)); + switch (settings.Provider) + { + case ChatProvider.Irc: + throw new NotImplementedException(); + case ChatProvider.Discord: + throw new NotImplementedException(); + default: + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid ChatProvider: {0}", settings.Provider)); + } + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs new file mode 100644 index 0000000000..278ffa5813 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.Chat.Providers +{ + /// + /// For interacting with a chat service + /// + interface IProvider : IDisposable + { + /// + /// If the + /// + bool Connected { get; } + + /// + /// The that indicates the was mentioned + /// + string BotMention { get; } + + /// + /// Get a resulting in the next the recieves or on a disconnect + /// + Task NextMessage { get; } + + /// + /// Attempt to connect the + /// + /// The for the operation + /// A resulting in on success, otherwise + Task Connect(CancellationToken cancellationToken); + + /// + /// Get the s for given + /// + /// The s to map + /// The for the operation + /// A resulting in a of the s representing + Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken); + + /// + /// Send a message to the + /// + /// The to send to + /// The message contents + /// The for the operation + /// A representing the running operation + Task SendMessage(long channelId, string message, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs b/src/Tgstation.Server.Host/Components/Chat/Response.cs similarity index 93% rename from src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs rename to src/Tgstation.Server.Host/Components/Chat/Response.cs index 67f8cf6782..855752e790 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatResponse.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Response.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components.Chat /// /// Represents a chat message requested by DD /// - sealed class ChatResponse + sealed class Response { /// /// The message string diff --git a/src/Tgstation.Server.Host/Components/Chat/User.cs b/src/Tgstation.Server.Host/Components/Chat/User.cs new file mode 100644 index 0000000000..cc081dfaa9 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/User.cs @@ -0,0 +1,10 @@ +namespace Tgstation.Server.Host.Components.Chat +{ + class User + { + long Id { get; set; } + string FriendlyName { get; set; } + string Mention { get; set; } + Channel channel { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 4d61522620..4937f7e170 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -102,9 +102,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly ISession session; /// - /// The for the + /// The for the /// - readonly IChatJsonTrackingContext chatJsonTrackingContext; + readonly IJsonTrackingContext chatJsonTrackingContext; /// /// The for the @@ -154,7 +154,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public SessionController(ReattachInformation reattachInformation, ISession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, IChatJsonTrackingContext chatJsonTrackingContext, IChat chat, ILogger logger) + public SessionController(ReattachInformation reattachInformation, ISession session, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, IJsonTrackingContext chatJsonTrackingContext, IChat chat, ILogger logger) { this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index c0f07beaa5..70ce656dc0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -539,10 +539,10 @@ namespace Tgstation.Server.Host.Components.Watchdog if (results == null) return; - List responses; + List responses; try { - responses = JsonConvert.DeserializeObject>(results); + responses = JsonConvert.DeserializeObject>(results); } catch { diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index 8f57e1e885..3bd53864fd 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -9,7 +9,7 @@ public long Id { get; set; } /// - /// The + /// The /// public long ChatSettingsId { get; set; } diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs index 5d3df56af5..1152181268 100644 --- a/src/Tgstation.Server.Host/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -5,12 +5,7 @@ namespace Tgstation.Server.Host.Models { /// public sealed class ChatSettings : Api.Models.Internal.ChatSettings - { - /// - /// The row Id - /// - public long Id { get; set; } - + { /// /// The /// From 36e2c414b6294d1c85d8cf8baf08de4360f986c7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 15:58:27 -0400 Subject: [PATCH 214/397] Add Cyberboss.SmartIrc4Net.Standard --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 6aa781f294..aa1f136bcd 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -34,6 +34,7 @@ + @@ -61,8 +62,4 @@ - - - - From 2c57a059fae16374ada104105354def95e3afb03 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 13 Jul 2018 17:04:01 -0400 Subject: [PATCH 215/397] Bunch more work on chat stuff --- .../Components/Chat/Channel.cs | 2 +- .../Components/Chat/ChannelMapping.cs | 2 + .../Components/Chat/Chat.cs | 72 +++++++++++++++++-- .../Components/Chat/Commands/Command.cs | 11 +++ .../Components/Chat/Commands/CustomCommand.cs | 23 ++++++ .../Components/Chat/IChat.cs | 6 ++ .../Components/Chat/ICommandFactory.cs | 10 +++ .../Components/Chat/ICustomCommandHandler.cs | 22 ++++++ .../Components/Chat/IJsonTrackingContext.cs | 6 ++ .../Components/Chat/JsonTrackingContext.cs | 63 ++++++++++++++++ .../Components/Chat/User.cs | 2 +- .../Components/Watchdog/Watchdog.cs | 19 ++++- 12 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Channel.cs b/src/Tgstation.Server.Host/Components/Chat/Channel.cs index d9916a1014..33b0e6f7db 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Channel.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Channel.cs @@ -1,6 +1,6 @@ namespace Tgstation.Server.Host.Components.Chat { - sealed class Channel + public sealed class Channel { public long Id { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs index cdf098287a..243bf71206 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs @@ -5,5 +5,7 @@ public long ProviderId { get; set; } public long ProviderChannelId { get; set; } public bool IsWatchdogChannel { get; set; } + + public Channel Channel { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 86cc30b99c..9f3e8e4324 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -1,9 +1,12 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Chat.Providers; using Tgstation.Server.Host.Core; @@ -22,6 +25,11 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly IIOManager ioManager; + /// + /// s that never change + /// + readonly IReadOnlyList builtinCommands; + /// /// Map of s in use, keyed by /// @@ -32,6 +40,13 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly Dictionary mappedChannels; + readonly List trackingContexts; + + /// + /// The for the + /// + ICustomCommandHandler customCommandHandler; + /// /// Used for remapping s /// @@ -42,13 +57,16 @@ namespace Tgstation.Server.Host.Components.Chat /// /// The value of /// The value of - public Chat(IProviderFactory providerFactory, IIOManager ioManager) + /// The used to populate + public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory) { this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + builtinCommands = commandFactory?.GenerateCommands() ?? throw new ArgumentNullException(nameof(commandFactory)); providers = new Dictionary(); mappedChannels = new Dictionary(); + trackingContexts = new List(); channelIdCounter = 1; } @@ -68,6 +86,12 @@ namespace Tgstation.Server.Host.Components.Chat lock (providers) if (!providers.TryGetValue(connectionId, out provider)) return; + lock (mappedChannels) + foreach (var kvp in mappedChannels.Where(x => x.Value.ProviderId == connectionId)) + { + mappedChannels.Remove(kvp.Key); + + } var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false); if (results == null) //aborted return; @@ -75,7 +99,8 @@ namespace Tgstation.Server.Host.Components.Chat { IsWatchdogChannel = x.IsWatchdogChannel, ProviderChannelId = y.Id, - ProviderId = connectionId + ProviderId = connectionId, + Channel = y }); long baseId; @@ -84,14 +109,24 @@ namespace Tgstation.Server.Host.Components.Chat baseId = channelIdCounter; channelIdCounter += results.Count; } + + Task task; lock (mappedChannels) { lock (providers) if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) //aborted again return; foreach (var I in mappings) - mappedChannels.Add(baseId++, I); + { + var newId = baseId++; + mappedChannels.Add(newId, I); + I.Channel.Id = newId; + } + + lock (trackingContexts) + task = Task.WhenAll(trackingContexts.Select(x => x.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken))); } + await task.ConfigureAwait(false); } /// @@ -159,10 +194,25 @@ namespace Tgstation.Server.Host.Components.Chat public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; /// - public Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken) + public async Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken) { - ioManager.ResolvePath("."); - throw new NotImplementedException(); + if (customCommandHandler == null) + throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!"); + JsonTrackingContext context = null; + context = new JsonTrackingContext(ioManager, customCommandHandler, () => + { + lock (trackingContexts) + trackingContexts.Remove(context); + }, ioManager.ConcatPath(basePath, commandsJsonName), ioManager.ConcatPath(basePath, channelsJsonName)); + Task task; + lock (trackingContexts) + { + trackingContexts.Add(context); + lock (mappedChannels) + task = Task.WhenAll(trackingContexts.Select(x => x.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken))); + } + await task.ConfigureAwait(false); + return context; } /// @@ -171,5 +221,13 @@ namespace Tgstation.Server.Host.Components.Chat lock (providers) return providers.TryGetValue(connectionId, out var provider) && provider.Connected; } + + /// + public void RegisterCommandHandler(ICustomCommandHandler customCommandHandler) + { + if (this.customCommandHandler != null) + throw new InvalidOperationException("RegisterCommandHandler() already called!"); + this.customCommandHandler = customCommandHandler ?? throw new ArgumentNullException(nameof(customCommandHandler)); + } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs new file mode 100644 index 0000000000..c607f04b45 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs @@ -0,0 +1,11 @@ +namespace Tgstation.Server.Host.Components.Chat.Commands +{ + abstract class Command + { + public string Name { get; set; } + public string HelpText { get; set; } + public bool AdminOnly { get; set; } + + public abstract void Invoke(string arguments); + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs new file mode 100644 index 0000000000..e2d9b5a4a1 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs @@ -0,0 +1,23 @@ +using System; + +namespace Tgstation.Server.Host.Components.Chat.Commands +{ + sealed class CustomCommand : Command + { + ICustomCommandHandler handler; + + public void SetHandler(ICustomCommandHandler handler) + { + if (this.handler != null) + throw new InvalidOperationException("SetHandler() already called!"); + this.handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + /// + public override void Invoke(string arguments) + { + if (handler == null) + throw new InvalidOperationException("SetHandler() has not been called!"); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/IChat.cs b/src/Tgstation.Server.Host/Components/Chat/IChat.cs index 26eacbfd4f..a96d4b4fea 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChat.cs @@ -19,6 +19,12 @@ namespace Tgstation.Server.Host.Components.Chat /// if it is connected, otherwise bool Connected(long connectionId); + /// + /// Registers a to use + /// + /// A + void RegisterCommandHandler(ICustomCommandHandler customCommandHandler); + /// /// Change chat settings. If the is not currently in use, a new connection will be made instead /// diff --git a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs new file mode 100644 index 0000000000..4020ab0613 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Tgstation.Server.Host.Components.Chat.Commands; + +namespace Tgstation.Server.Host.Components.Chat +{ + interface ICommandFactory + { + IReadOnlyList GenerateCommands(); + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs b/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs new file mode 100644 index 0000000000..5106ba0efd --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.Chat +{ + /// + /// Handles that map to those defined in a + /// + public interface ICustomCommandHandler + { + /// + /// Handle a chat command + /// + /// The command name + /// Everything typed after minus leading spaces + /// The sending + /// The for the operation + /// A resulting in the response text to send back + Task HandleChatCommand(string commandName, string arguments, User sender, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs index 5988619602..a4273ee79c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Chat.Commands; namespace Tgstation.Server.Host.Components.Chat { @@ -7,5 +11,7 @@ namespace Tgstation.Server.Host.Components.Chat /// public interface IJsonTrackingContext : IDisposable { + Task> GetCustomCommands(CancellationToken cancellationToken); + Task SetChannels(IEnumerable channels, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs new file mode 100644 index 0000000000..c231ea3ef7 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Chat.Commands; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Chat +{ + /// + sealed class JsonTrackingContext : IJsonTrackingContext + { + readonly IIOManager ioManager; + readonly ICustomCommandHandler customCommandHandler; + readonly Action onDispose; + + readonly string commandsPath; + readonly string channelsPath; + + readonly SemaphoreSlim channelsSemaphore; + + public JsonTrackingContext(IIOManager ioManager, ICustomCommandHandler customCommandHandler, Action onDispose, string commandsPath, string channelsPath) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.customCommandHandler = customCommandHandler ?? throw new ArgumentNullException(nameof(customCommandHandler)); + this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); + this.commandsPath = commandsPath ?? throw new ArgumentNullException(nameof(commandsPath)); + this.channelsPath = channelsPath ?? throw new ArgumentNullException(nameof(channelsPath)); + + channelsSemaphore = new SemaphoreSlim(1); + } + + /// + public void Dispose() => onDispose(); + + /// + public async Task> GetCustomCommands(CancellationToken cancellationToken) + { + try + { + var resultBytes = await ioManager.ReadAllBytes(commandsPath, cancellationToken).ConfigureAwait(false); + var resultJson = Encoding.UTF8.GetString(resultBytes); + var result = JsonConvert.DeserializeObject>(resultJson); + foreach (var I in result) + I.SetHandler(customCommandHandler); + return result; + } + catch + { + return new List(); + } + } + + /// + public async Task SetChannels(IEnumerable channels, CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(channelsSemaphore, cancellationToken).ConfigureAwait(false)) + await ioManager.WriteAllBytes(channelsPath, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(channels)), cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/User.cs b/src/Tgstation.Server.Host/Components/Chat/User.cs index cc081dfaa9..7b716dc419 100644 --- a/src/Tgstation.Server.Host/Components/Chat/User.cs +++ b/src/Tgstation.Server.Host/Components/Chat/User.cs @@ -1,6 +1,6 @@ namespace Tgstation.Server.Host.Components.Chat { - class User + public sealed class User { long Id { get; set; } string FriendlyName { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 70ce656dc0..f4a19fad3d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -15,7 +15,7 @@ using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class Watchdog : IWatchdog, IEventConsumer + sealed class Watchdog : IWatchdog, IEventConsumer, ICustomCommandHandler { /// /// The time in milliseconds to wait from starting to start . Does not take responsiveness into account @@ -132,6 +132,8 @@ namespace Tgstation.Server.Host.Components.Watchdog serverUpdater.RegisterForUpdate(() => releaseServers = true); + chat.RegisterCommandHandler(this); + AlphaIsActive = true; ActiveLaunchParameters = initialLaunchParameters; releaseServers = false; @@ -552,5 +554,20 @@ namespace Tgstation.Server.Host.Components.Watchdog await Task.WhenAll(responses.Select(x => chat.SendMessage(x.Message, x.ChannelIds, cancellationToken))).ConfigureAwait(false); } + + /// + public async Task HandleChatCommand(string commandName, IEnumerable arguments, Chat.User sender, CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + if (!Running) + return "ERROR: Server offline!"; + + var command = String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", byondTopicSender.SanitizeString(InteropConstants.DMTopicChatCommand), byondTopicSender.SanitizeString(InteropConstants.DMParameterData), byondTopicSender.SanitizeString(JsonConvert.SerializeObject(arguments))); + + var activeServer = AlphaIsActive ? alphaServer : bravoServer; + return await activeServer.SendCommand(command, cancellationToken).ConfigureAwait(false) ?? "ERROR: Bad topic exchange!"; + } + } } } From 88d6ff42fbfea32bc2705a9c0826c92a669f8661 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 16 Jul 2018 01:23:51 -0400 Subject: [PATCH 216/397] Fixes and comments --- UpgradeLog.htm | Bin 0 -> 41384 bytes .../Components/Chat/Channel.cs | 19 ++++++++++++++++-- .../Components/Chat/Commands/Command.cs | 6 +++--- .../Components/Chat/Commands/CustomCommand.cs | 2 +- .../Components/Watchdog/Watchdog.cs | 2 +- 5 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 UpgradeLog.htm diff --git a/UpgradeLog.htm b/UpgradeLog.htm new file mode 100644 index 0000000000000000000000000000000000000000..1b7f8648c7794d8114bd8e89b04958735798ae8d GIT binary patch literal 41384 zcmeI5`BNLolE?ekw-Nh4Xw1GbvmOqC&Ea|WgaCmthq(;4#}h9w;y#TyJ@ek5zPq1q zCFSaFiI!Rj?1@zj%FN2FZvF57{3H4ynuy*+?4jLUhjALG&W(j{5l&qo>hN)ceHUbKpFW zcB3PXo76hu_ly>TX0QuD_q`ROE7$80{XFNa%5~L2e&qT%qO=-qP=AxV>zwy++@(aA zRQYZJy@zYz_57ywS@ej$)XOGqD(?Ky;js=Ts%zNk_XTw~x$dFAD(6Otv*?79&FC|w zyNqA8#uM#&qFG=cMcdR^=XcY!?Q7VVsB%TWeP3zti^FLLD5qc`YP|sF86)d~vO~FW zT8XZ~JdVp5_@6=1O?vq!$79An4fn%{#9j5g>&Cal)d^>(wCbmC&$9yUlb<QoCN8Gz@>;7yUOa#=x}#1WBrg^fOmmZVUy= zBn1V&;`T21>Z-!s4X7-sP05L+x$uivw?w!bg!f zwL-leN;SWAJ>__^$*i;MRjyANvu2x>NGC~S^zhZaUHWQm{3k%F@R7dSc3Aee2uawV z0z;5A+PrZ7<@lFJnoRy*K>>|HbSTnqau!IZdEG58hxs6Ug{w+}hR8b$cIYz?&aKUW zS}ql#?+JQTbhcwC@n@|`M37%;#4k`!+#Fz>Cs3+2nDu6`*{t~7E7Obrw%U()Y_ibf ztnnE*n`JDx5r|@4ShC{HcYteI(F4X>VRZR4u-8y&EiwRCifASsTN}w;XOqV)aPtyQ z%3jIy5J!&~^{LZ22RU(U$4oW4$L&?^OnrohX14hP%r~P;=Ltyj$rn3j9I|Yh)0*QO zT$>fHQj&ij4?Ms;4%~>(tztBChq)DCwvWSJdg|$&XJ7SBAqwZ;M#|eEiGxmlvL&&K z*YWAb+Q#Kf)>`-EpBJzvlDrcn@;KVNkx0!mc>%Kc`DJqOL1nW2@)Kk;Desb-R&7bU zG~<2Cb~85b3|TQxPT0sZm*<#oF10!bw3;*PE6F|Jqm`enp0u`=Z?x|uTK=r$H(0s+ zvb_EPk{s}pee1#k529HIu=M2zqNzOjPtZ=(3Vx`!NBMkpPND z?maDijHU-ZNfT_N0W_y1*@`!{#-RXJ9=f<5kDYj*RJx`$_(1~NZ^>${#UOH})x^-v zF?pSD1o?G%Qr;>Lpk2)qmyf;Sq#`XFT4$ZK$hpSU^`533^HJAJb*<+h4z>m>uGJ*D zS{+Lo!dg-m=8n_MMeWT)e~dN@Yv0F| zd(EV{zC}sp4Ou&tS(L4SAFueMuC)I2apU~nP_M(+XgPaB$@{k$wf@mvW1Y84XBpYI z&qRwe%=4tj#dF$cE%Hl$J#IZmg1rQM3(mFb*?L`gJLe|RD*BA_X-zkNKb$kYHO_q1 zthQK@$4;6g#GHI2*m5PFvjyh3MV_QtHh`dYlFow$lVl3Ik0xuaU*%&tG4q+EDWp+U z*EEb^C?1e+P0;H`q3l_FOp0&czYKc1rR~0`;;EZYUh>aGeFyg(TOQWL%Kn-c9ADen3Q1Vxjq3EK@D?W}Rrk-qOJupiICq>c zuPi}-^!J=|yj7kpK*Nw{GP~__dBM{9x#V+_>u$B-=dE#F8JAY(PM4c~CaL<(H}n2* z{P`QLn)}pxv>{J3aN{c$iB0(2jh*GqKeWmXKZCrU^IFbkfsbG)Pp@$(-flWio>h<{ zX;58BqQp_n$aUuhhdT0;wzxBjxB7RE`E_0w5RzGdIF=7RYu3>h@>KI_IxjOupPm>E z<;xEfN7p>eCy{sMeNs0Xf4zp&mG`Wr`!2b+sn`3p^)j-!!N>fKGV@eW6N=d3s#6fjmD zjaZQu+jo;!zj?Vf+Wy8I(dQlT4jAz&kQIBbvQ$ zBYu914(+0~c4$e4CH0b z`W9-RTLMr;B8TP4ZFI(y6Gta2rB9Uc& zw{ObEzTPcGi>z5o?6&pwx6s>t_=P)POUB{y7@S$-ICoR3EQYmsgN`rJ=PEs4LdQ=x zZF;B_)neqItd-jzSllT>(j0kATe-7(Zc*%RsrN z_872BTyJx>4g4vt6savyW1s%D*L98aS!8aOI?_fJ>YdVhj(rLhMmJ3li|m75W1LU9 za|ER}z-5074Sklu;M}#@ru_jl z5In_f-q6=FqdTOpL7=?lIK-VrV9#(q%Q!Z{_ch$V;8#09M1dz@S#o@PO|2F1oB=QS z8fETX(B}p8mqxn+hW3hl0;6eY^&Sj1sWT2e6eWHKj%D!O0>5K$SOK33y$=ERHGL@G z-N&6BN>*r7q*u*?EpYrq9qn*E_eC?pTrPmVInujZn??cob zhr08O?iHniv`(u-Xi$c#7r<1!Q2PZmE6eo14aU>pH%!?I^{%1R7JS+UhYju>)0h0m z1Gsn$)^kAFW2{F|`3u*Qk`;Q`qNEIs78u(ZH0cMv|484gI(;<5cr@!I?Jt}r&13sp z_2#`-ZM*2ar*%Jmj_)gJ(ejMaA{gGfHhb)NIDd5y$4gF9N;{s8Jh_#Ny}XE~UqYu2qxmk;k6+Nx$LPd8XB}tIz7;gx5!$=L_nbRR=wCg_G(zh+ zEJcxWc_B}LbwRBvW$TQ0k-o>!(i>cl1Ah&TI_|8FwEZG5H_*wGz&ztj{`Df7T3D=8 zcOI>*XplUBeP=^P@fYTRGenvE;!U7wEutrxX2IYMcyDl2taFmHD@HJh-rjVh8ppEF z@coQkkY_kTzp|S0)MQ;(xn7{n27Ml23)JU4BORcX#&Jd2r0a8;*78%TuBKqEG1_-v zG78jD?(~7v1!vnp(H>}dKNFm7xR%?{c!}2XbGGPfk5*%}c?JBUyCbT8hMx0`U=A4C z?Xb>$+57`2Izu0e&LU1@6Lno>Oj;XGLDf-4GwN)0KYe}x_CB>0VeJFc5o#@QeMy@| zN9h4d2Ej}o_5gNt6+GoResN{*xi=5A19&zLv=5B!3HRioj5x~3`fr2hAtgn|Sq7JD zxbX!nw5plmT;;O*;=Fcb$#a{g&6Sg?3(B@QyXLOAybAT6f%~wNvKh*U8P}r2XqoYUE-)H#4uMSbnYVdM^-{weT{pHZY^RB*3j1P zoc5R2e}~nW2B&LUKI8bAUIyv?jJjH>zM-`uCR(p667!6jAAm53-XG^P!~OR_(Oh11 z7WOIjvCIfQqy1+&o@1ddu_6=Tbn-b+ufTCy5w6b3X6L}+5 zXts|9QEXrVoGySf#MrdjKB1N{Rg7hbI$AyVfzdu!f;U7T%Zx_Rk0aomyAeO5f91pF zL5$G4kK-DxXMm`(SG3R_`3oz+ctWr8e1zv2Bi{qgHRCyRmav~|MfbH{FEN@GU_YS; zt$z2o?&CN}eMK-8wK)PK-I-*ZeL$XsQcwB3=Kd=8BwM1Tw~k}nS)#==sCo)c;-o$= z9&p7QGC%iY8w32L9_*}hl|%QDcgb)klJ8^iK9?F~r(+YpolR6UI=#02j8Mc$(0M$pP7wDcvvT2l<5E6=H?2z3elsqKt1jo^w6VRR#P$ zbN+#n&)7q)7iXwb<@klZUsI~M`4GDJh?a^wFN5n8I(v#9%UtcErN8hw0HY%1vy4Nl zM%ltuR*0v-_(av-g7+)UL#;x0{RDFD%+qvLCPXe zJ4>To5=F)kMyh4k4`X1vWsvOXt zZ<(G?bxv_zkjwO zo##)tx6HrgzxA6n7SY_El#h>JaX$GHA$Re@;hik)CZke_{@|O$OnP2#C9?5$!?E<9 zvPUi#Q;7c6V82`z=AuG(f>OPVMelV`97~$kQ~ak)3N+4t#JO=>AKlSoxOK$Hjq8sc z-!u<`RHdn^%zoAgE&p`ejN7CyMF-?@%L}Zfh4$43DkxJZe8iPacrM;$^`4(D+5L$^ zd8ngZqrMo-c#OLt3}ON+iz)mhh*gyiT%V^vs+p_ z>>Tp@LA66jI!Y}yOQidPUQ^Uu5nairo|cdv)NUMUgKWIz2_^N9G@!?G1sJv$dFiJ2 zF|eDzf!zJIkGsx$J>_BAQBrt-efMLPHmsEedDmymxVZMK;vdM)YgH03d`!Jl=0hW? zwz==^MK@G@fmfp)!Ml9_!8eJSo=DRCmbbWbQ|}2rQ8p#4m&WtmYa&k-MVM@VnXHT- zT>%`^c>M;xzq5GFvQqDX-v+*c6uG(87^faaR#kC4Yqsh`{o}M~p+fA#PT_^i)79O>l%RE}W zQ$ty~W4AugjvMV+jPu*HOI&+p>&Ry2tDmV!s+(kW^jCeIkTvhxufIHp>dm{`^=S6I zQ@gzL{~`2f0ZlAjTszkL)38a&4|>bqld3tX{SxvnG#m9Uv2JIZwBJD1IeQMqwT_)@ zpR?AGivGwGQjS9Jt!N%jw(e<0ds&?}V+HCx?dNq$z>k#b-^j=}y>#XN7 z`WQiaRjc)ZsBMaeIWT3#G(Vvv}^ObL+dG7BeJ-_V!jr;m-78BnhE!{TS=B3+YCyU0urvgzVJq4|3 znmn}d_F?f|ywjVA#WCZz)`}`ga@*fa%86g0=*B4wNI# z)((Mx&C#vCMD?a-cDK{02gFiMcSr^vv;NVur8}5hs+u5+&SM4{H#$`@9{=7-zsk?%L1Wr`-qwd+KsTl>UHzJQ?OtQdBoT3~)Qok@=8qs| z@ksac#J3(HlX1S;%5j`IqWp=ytN#fw>r=X};8dsb_V`m~1!K|9}7+X$y@jgxiPvhyLw zuKc|IamBQ?uC%X0yZd!+@6^fr?j{O^o`W6m~Z(mR*T`eD9JKREU+y9@9 z-W#9B^Nq^@pE}u6WM>5!_WvqsmA-3q7^vH4`b{&|t8%-RhZ3HWKRfQYqyEpu&MYgI z#%{ssdGA++oL1a&s-I*%Vc7_-vf$d8>anYZXf+zc3VY|O8Svt@8pBNPQ_q5%uGtuN z(kf=bPS=dX7KJ@8{ZV>LPe7T6B{{6!Usy&i!~RR{Ua@~Y(sR|43`K3F*OZ^rb$a#M z1&yb$A9_EF{>4S|B|kvfdP&0VcZI#yx;mzN(P*rHJKhfEV@40XE>fcBy)}Y8*HavB znmRr%H4gSRTjf(%+&J=$JV=*lP;@=}vEBuPm&8`$bh`_zWE7&yjj}%Ue6p2%Q_9`t zOBq4DucEb9ra8~;?jvPnrEDt9`iw+09zW2x5^?tkO zrYwhaq-Lc*&%?!dn+_j6|NQac_^9WsOL%Si7q^#0bp2HxUM5?0xaqm*j}OO9d0*T3 zRvWB{#LxJ39*(Blb$IG|>5mV`(`7WR-rW;+H&1h}e2ikJFY&-{zn_Qajo@@-!1LZ8 zA5I3&nAOH@JIlk@(4xaZ&pCg5I1Vb0rFm*x3|DzLnQYbJrstwRJ{&hMct%S9C2Iey z9Io@)KbPRb)K`&f;h)ZXBqQ%DUvZ81ubz|jG|PSeK^AOmaax9Gv5g_* z-EJSbALs4*F+fenU_~Lt@#8q$-cx`RIIGcw__yPIE69bOSJ8hrYB#QQN6@L-W4YtX{Ig-o^rm-#D1U3u1>62`?TNGZHH$0KN`j-3 z@D^=77p3RKDro8g^I2CJSuTd|M5ma{kK+6DK5r@4plboBoYMRqLe zxhMTYUQ)Aodc0u&=ODH9RzAHAVSo(j2oa_cN_uz#e1tu+qpqw7)MuROdGrx}2YL2P z@8Fr>{sf;|OX(#q%qX43wMSaElTrU5#io0bchz4rIw{#It_S`~ddPI;?K(s`JxOd@ zgT%9h&CdQ>t*ZA4CN*aD=6OYFw9FP{ z$!9G5_)StmE}|L9vDzJp30dhR7qjeew!|1^zYj_AnT@oz@#W3x8DGC6FYT8XPuuT9 zVnY5TEi>Uxvr-dUbmXS}aucZgeMn9`Pm`9N_`PPOC*GzbKkb*FIDNkl395}8#Is9D zZzrp6vl5mCqa$(cm$)o?e;*R)b6ZJS3t6jXJZr1nk)QUROyElo+$P8q3dmxobaQ`}OP$lgq*Jj#)yuI+JFW-))X z+)GJ5p4xLb?aj0)PvT{<{=R{`65MdZVoNjejPar8gU^ zhB8yzoCgeho67!}3^dQhe0s(|Z(S=n51Bjh5|q|69=rd)dd5@We)UW`!>Iq`&=a2G SOY%JaB&srrXUO#d_5Kfj4hm2J literal 0 HcmV?d00001 diff --git a/src/Tgstation.Server.Host/Components/Chat/Channel.cs b/src/Tgstation.Server.Host/Components/Chat/Channel.cs index 33b0e6f7db..8a6cfc1d66 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Channel.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Channel.cs @@ -1,14 +1,29 @@ namespace Tgstation.Server.Host.Components.Chat { + /// + /// Represents a channel + /// public sealed class Channel { + /// + /// The channel Id. + /// + /// remaps this to an internal id using public long Id { get; set; } + /// + /// The user friendly name of the + /// public string FriendlyName { get; set; } - public string ConnectionName { get; set; } - + /// + /// If this is considered a channel for admin commands + /// public bool IsAdminChannel { get; set; } + + /// + /// If this i + /// public bool IsPrivateChannel { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs index c607f04b45..8ad981aee0 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs @@ -1,11 +1,11 @@ namespace Tgstation.Server.Host.Components.Chat.Commands { - abstract class Command - { + public abstract class Command + { public string Name { get; set; } public string HelpText { get; set; } public bool AdminOnly { get; set; } public abstract void Invoke(string arguments); - } + } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs index e2d9b5a4a1..94e8af328f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs @@ -2,7 +2,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands { - sealed class CustomCommand : Command + public sealed class CustomCommand : Command { ICustomCommandHandler handler; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index f4a19fad3d..c64d6212c7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -556,7 +556,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public async Task HandleChatCommand(string commandName, IEnumerable arguments, Chat.User sender, CancellationToken cancellationToken) + public async Task HandleChatCommand(string commandName, string arguments, Chat.User sender, CancellationToken cancellationToken) { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { From 9bbe7135f0517b801fefa8a76680bfe67fa5b5c5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Jul 2018 15:52:12 -0400 Subject: [PATCH 217/397] Get things building again --- src/DMAPI/tgs.dm | 3 +- .../Models/ChatChannel.cs | 2 +- .../Components/Chat/Channel.cs | 13 ++-- .../Components/Chat/Chat.cs | 67 ++++++++++++++----- .../Components/Chat/Commands/Command.cs | 18 +++++ .../Components/Chat/Commands/CustomCommand.cs | 10 +++ .../Components/Chat/IChat.cs | 8 +++ .../Components/Chat/IJsonTrackingContext.cs | 12 ++++ .../Components/Chat/Providers/IProvider.cs | 7 ++ .../Components/Chat/User.cs | 5 +- 10 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 4e9749084b..c3daf1a380 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -97,8 +97,7 @@ /datum/tgs_chat_channel var/id //internal channel representation var/friendly_name //user friendly channel name - var/server_name //server name the channel resides on - var/provider_name //chat provider for the channel + var/connection_name //the name of the configured chat connection var/is_admin_channel //if the server operator has marked this channel for game admins only var/is_private_channel //if this is a private chat channel diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index c7ab3b9b32..942154e506 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -13,7 +13,7 @@ /// /// The Discord channel ID /// - public long DiscordChannelId { get; set; } + public long? DiscordChannelId { get; set; } /// /// If the is an admin channel diff --git a/src/Tgstation.Server.Host/Components/Chat/Channel.cs b/src/Tgstation.Server.Host/Components/Chat/Channel.cs index 8a6cfc1d66..c2eab84dd8 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Channel.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Channel.cs @@ -17,13 +17,18 @@ public string FriendlyName { get; set; } /// - /// If this is considered a channel for admin commands + /// The name of the connection the belongs to /// - public bool IsAdminChannel { get; set; } + public string ConnectionName { get; set; } /// - /// If this i + /// If this is considered a channel for admin commands /// - public bool IsPrivateChannel { get; set; } + public bool IsAdmin { get; set; } + + /// + /// If this is a 1-to-1 chat channel + /// + public bool IsPrivate { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 9f3e8e4324..862096ae44 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -1,8 +1,6 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; @@ -40,6 +38,9 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly Dictionary mappedChannels; + /// + /// The active s for the + /// readonly List trackingContexts; /// @@ -52,6 +53,11 @@ namespace Tgstation.Server.Host.Components.Chat /// long channelIdCounter; + /// + /// If has been called + /// + bool started; + /// /// Construct a /// @@ -77,21 +83,43 @@ namespace Tgstation.Server.Host.Components.Chat I.Value.Dispose(); } + /// + /// Remove a from and optionally updating the as well + /// + /// The of the to delete + /// If should be update + /// The for the operation + /// A resulting in the being removed if it exists, otherwise + async Task RemoveProvider(long connectionId, bool updateTrackings, CancellationToken cancellationToken) + { + IProvider provider; + lock (providers) + if (!providers.TryGetValue(connectionId, out provider)) + return null; + Task task; + lock (mappedChannels) + { + foreach (var kvp in mappedChannels.Where(x => x.Value.ProviderId == connectionId)) + mappedChannels.Remove(kvp.Key); + + if (updateTrackings) + lock (trackingContexts) + task = Task.WhenAll(trackingContexts.Select(x => x.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken))); + else + task = Task.CompletedTask; + } + await task.ConfigureAwait(false); + return provider; + } + /// public async Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken) { if (newChannels == null) throw new ArgumentNullException(nameof(newChannels)); - IProvider provider; - lock (providers) - if (!providers.TryGetValue(connectionId, out provider)) - return; - lock (mappedChannels) - foreach (var kvp in mappedChannels.Where(x => x.Value.ProviderId == connectionId)) - { - mappedChannels.Remove(kvp.Key); - - } + var provider = await RemoveProvider(connectionId, false, cancellationToken).ConfigureAwait(false); + if (provider == null) + return; var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false); if (results == null) //aborted return; @@ -152,7 +180,7 @@ namespace Tgstation.Server.Host.Components.Chat lock (mappedChannels) foreach (var channelId in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key)) mappedChannels.Remove(channelId); - if (newSettings.Enabled) + if (newSettings.Enabled && started) await provider.Connect(cancellationToken).ConfigureAwait(false); } @@ -188,10 +216,14 @@ namespace Tgstation.Server.Host.Components.Chat } /// - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))); + public async Task StartAsync(CancellationToken cancellationToken) + { + await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))).ConfigureAwait(false); + started = true; + } /// - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Disconnect(cancellationToken))); /// public async Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken) @@ -229,5 +261,8 @@ namespace Tgstation.Server.Host.Components.Chat throw new InvalidOperationException("RegisterCommandHandler() already called!"); this.customCommandHandler = customCommandHandler ?? throw new ArgumentNullException(nameof(customCommandHandler)); } + + /// + public Task DeleteConnection(long connectionId, CancellationToken cancellationToken) => RemoveProvider(connectionId, true, cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs index 8ad981aee0..d5f192ec59 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs @@ -1,11 +1,29 @@ namespace Tgstation.Server.Host.Components.Chat.Commands { + /// + /// Represents a command that can be invoked by talking to chat bots + /// public abstract class Command { + /// + /// The text to invoke the command. May not be "?" or "help" (case-insensitive) + /// public string Name { get; set; } + + /// + /// The help text to display when queires are made about the command + /// public string HelpText { get; set; } + + /// + /// If the command should only be available to s who's has set + /// public bool AdminOnly { get; set; } + /// + /// Invoke the + /// + /// The text after with leading whitespace trimmed public abstract void Invoke(string arguments); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs index 94e8af328f..6221f531c1 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs @@ -2,10 +2,20 @@ namespace Tgstation.Server.Host.Components.Chat.Commands { + /// + /// Represents a command made from DM code + /// public sealed class CustomCommand : Command { + /// + /// The for the + /// ICustomCommandHandler handler; + /// + /// Set a new + /// + /// The value of public void SetHandler(ICustomCommandHandler handler) { if (this.handler != null) diff --git a/src/Tgstation.Server.Host/Components/Chat/IChat.cs b/src/Tgstation.Server.Host/Components/Chat/IChat.cs index a96d4b4fea..68c12aa1ed 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChat.cs @@ -33,6 +33,14 @@ namespace Tgstation.Server.Host.Components.Chat /// A representing the running operation. Will complete immediately if the property of is Task ChangeSettings(ChatSettings newSettings, CancellationToken cancellationToken); + /// + /// Disconnects and deletes a given connection + /// + /// The of the connection + /// The for the operation + /// A representing the running operation + Task DeleteConnection(long connectionId, CancellationToken cancellationToken); + /// /// Change chat channels /// diff --git a/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs index a4273ee79c..778b6d0d3c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IJsonTrackingContext.cs @@ -11,7 +11,19 @@ namespace Tgstation.Server.Host.Components.Chat /// public interface IJsonTrackingContext : IDisposable { + /// + /// Read s from the + /// + /// The for the operation + /// A resulting in a of s in the Task> GetCustomCommands(CancellationToken cancellationToken); + + /// + /// Writes information about connected to the + /// + /// The s to write out + /// The for the operation + /// A representing the running operation Task SetChannels(IEnumerable channels, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs index 278ffa5813..7abe357269 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -32,6 +32,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// A resulting in on success, otherwise Task Connect(CancellationToken cancellationToken); + /// + /// Gracefully disconnects the provider. Implies a call to + /// + /// The for the operation + /// A representing the running operation + Task Disconnect(CancellationToken cancellationToken); + /// /// Get the s for given /// diff --git a/src/Tgstation.Server.Host/Components/Chat/User.cs b/src/Tgstation.Server.Host/Components/Chat/User.cs index 7b716dc419..12eff58285 100644 --- a/src/Tgstation.Server.Host/Components/Chat/User.cs +++ b/src/Tgstation.Server.Host/Components/Chat/User.cs @@ -1,10 +1,13 @@ namespace Tgstation.Server.Host.Components.Chat { + /// + /// + /// public sealed class User { long Id { get; set; } string FriendlyName { get; set; } string Mention { get; set; } - Channel channel { get; set; } + Channel Channel { get; set; } } } From 4ac2606c65fb9a95c12ac2265a67b29e834fe92b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Jul 2018 16:14:18 -0400 Subject: [PATCH 218/397] ChatController Create --- .../Rights/ChatSettingsRights.cs | 10 +- .../Controllers/ChatController.cs | 97 +++++++++++++++++++ .../Controllers/DreamDaemonController.cs | 4 +- .../Models/DatabaseContext.cs | 4 +- .../Models/IDatabaseContext.cs | 5 + 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/ChatController.cs diff --git a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs index e8803311ac..c4336d76dd 100644 --- a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs @@ -39,6 +39,14 @@ namespace Tgstation.Server.Api.Rights /// /// User can change /// - WriteName = 32 + WriteName = 32, + /// + /// User can create new + /// + Create = 64, + /// + /// User can delete + /// + Delete = 128 } } diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs new file mode 100644 index 0000000000..22f1d75cb4 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// for managing + /// + [TgsAuthorize] + public sealed class ChatController : ModelController + { + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + public ChatController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) + { + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + + /// + [TgsAuthorize(ChatSettingsRights.Create)] + public override async Task Create([FromBody] Api.Models.ChatSettings model, CancellationToken cancellationToken) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + + if (String.IsNullOrWhiteSpace(model.Name)) + return BadRequest(new { message = "Name cannot be null or whitespace!" }); + + if (String.IsNullOrWhiteSpace(model.ConnectionString)) + return BadRequest(new { message = "ConnectionString cannot be null or whitespace!" }); + + //try to update das db first + var dbModel = new Models.ChatSettings + { + Name = model.Name, + ConnectionString = model.ConnectionString, + Enabled = model.Enabled, + Channels = model.Channels?.Select(x => new Models.ChatChannel + { + DiscordChannelId = x.DiscordChannelId, + IrcChannel = x.IrcChannel, + IsAdminChannel = x.IsAdminChannel, + IsWatchdogChannel = x.IsWatchdogChannel + }).ToList() ?? new List(), + InstanceId = Instance.Id, + Provider = model.Provider, + }; + DatabaseContext.ChatSettings.Add(dbModel); + DatabaseContext.ChatChannels.AddRange(dbModel.Channels); + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + + try + { + try + { + //try to create it + var instance = instanceManager.GetInstance(Instance); + await instance.Chat.ChangeSettings(dbModel, cancellationToken).ConfigureAwait(false); + + if (dbModel.Channels.Count > 0) + await instance.Chat.ChangeChannels(dbModel.Id, dbModel.Channels, cancellationToken).ConfigureAwait(false); + } + catch + { + //undo the add + DatabaseContext.ChatSettings.Remove(dbModel); + DatabaseContext.ChatChannels.RemoveRange(dbModel.Channels); + await DatabaseContext.Save(default).ConfigureAwait(false); + throw; + } + } + catch (InvalidOperationException e) + { + return BadRequest(new { message = e.Message }); + } + return Json(dbModel); + } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 168d3a46da..c1a4a6b673 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -18,10 +18,10 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { /// - /// for managing + /// for managing the /// [Route("/" + nameof(DreamDaemon))] - public sealed class DreamDaemonController : ModelController + public sealed class DreamDaemonController : ModelController { /// /// The for the diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 554118c0f4..ff966eba6f 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -52,9 +52,7 @@ namespace Tgstation.Server.Host.Models /// public DbSet InstanceUsers { get; set; } - /// - /// The s in the - /// + /// public DbSet ChatChannels { get; set; } /// diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index e08826dcc9..9e8d906905 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -49,6 +49,11 @@ namespace Tgstation.Server.Host.Models /// DbSet ChatSettings { get; set; } + /// + /// The in the + /// + DbSet ChatChannels { get; set; } + /// /// The in the /// From 9e58ccbc30288c7d58306d95eef569013fe08efb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Jul 2018 16:17:27 -0400 Subject: [PATCH 219/397] ChatController.Delete --- .../Controllers/ChatController.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 22f1d75cb4..6e30d57aee 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -9,6 +9,7 @@ using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; +using Z.EntityFramework.Plus; namespace Tgstation.Server.Host.Controllers { @@ -82,7 +83,6 @@ namespace Tgstation.Server.Host.Controllers { //undo the add DatabaseContext.ChatSettings.Remove(dbModel); - DatabaseContext.ChatChannels.RemoveRange(dbModel.Channels); await DatabaseContext.Save(default).ConfigureAwait(false); throw; } @@ -93,5 +93,18 @@ namespace Tgstation.Server.Host.Controllers } return Json(dbModel); } + + /// + [TgsAuthorize(ChatSettingsRights.Delete)] + public override async Task Delete([FromBody] Api.Models.ChatSettings model, CancellationToken cancellationToken) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + + var instance = instanceManager.GetInstance(Instance); + await Task.WhenAll(instance.Chat.DeleteConnection(model.Id, cancellationToken), DatabaseContext.ChatSettings.Where(x => x.Id == model.Id).DeleteAsync(cancellationToken)).ConfigureAwait(false); + + return Ok(); + } } } From 9ec064c6029448c452f373576b3cb20a5e3c932a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Jul 2018 16:26:07 -0400 Subject: [PATCH 220/397] Chat controller list --- .../Models/Internal/ChatSettings.cs | 2 +- .../Controllers/ChatController.cs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs index 3357c507b1..db9ba2db13 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Manage the server chat bots /// - [Model(RightsType.ChatSettings, RequiresInstance = true, CanCrud = true, ReadRight = ChatSettingsRights.Read)] + [Model(RightsType.ChatSettings, RequiresInstance = true, CanList = true, CanCrud = true, ReadRight = ChatSettingsRights.Read)] public class ChatSettings { /// diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 6e30d57aee..3288e5d7f7 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; @@ -106,5 +107,22 @@ namespace Tgstation.Server.Host.Controllers return Ok(); } + + /// + [TgsAuthorize(ChatSettingsRights.Read)] + public override async Task List(CancellationToken cancellationToken) + { + var query = DatabaseContext.ChatSettings.Where(x => x.InstanceId == Instance.Id).Include(x => x.Channels); + + var results = await query.ToListAsync(cancellationToken).ConfigureAwait(false); + + var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatSettings) & (int)ChatSettingsRights.ReadConnectionString) != 0; + + if (!connectionStrings) + foreach (var I in results) + I.ConnectionString = null; + + return Json(results); + } } } From 99e10f905e1f2e80bde590e645deeec4291bad7f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 16 Jul 2018 17:02:07 -0400 Subject: [PATCH 221/397] ChatController.Update --- .../Models/Internal/ChatSettings.cs | 4 +- .../Components/Chat/Chat.cs | 4 +- .../Controllers/ChatController.cs | 94 +++++++++++++++++-- 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs index db9ba2db13..2a23052ad8 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatSettings.cs @@ -26,13 +26,13 @@ namespace Tgstation.Server.Api.Models.Internal /// If the connection is enabled /// [Permissions(WriteRight = ChatSettingsRights.WriteEnabled)] - public bool Enabled { get; set; } + public bool? Enabled { get; set; } /// /// The used for the connection /// [Permissions(WriteRight = ChatSettingsRights.WriteProvider)] - public ChatProvider Provider { get; set; } + public ChatProvider? Provider { get; set; } /// /// The information used to connect to the diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 862096ae44..07a29865c9 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -171,7 +171,7 @@ namespace Tgstation.Server.Host.Components.Chat providers.Remove(newSettings.Id); provider.Dispose(); } - if (newSettings.Enabled) + if (newSettings.Enabled.Value) { provider = providerFactory.CreateProvider(newSettings); providers.Add(newSettings.Id, provider); @@ -180,7 +180,7 @@ namespace Tgstation.Server.Host.Components.Chat lock (mappedChannels) foreach (var channelId in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key)) mappedChannels.Remove(channelId); - if (newSettings.Enabled && started) + if (newSettings.Enabled.Value && started) await provider.Connect(cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 3288e5d7f7..2a19a797e3 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -36,6 +39,14 @@ namespace Tgstation.Server.Host.Controllers this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); } + static Models.ChatChannel ConvertApiChatChannel(Api.Models.ChatChannel api) => new Models.ChatChannel + { + DiscordChannelId = api.DiscordChannelId, + IrcChannel = api.IrcChannel, + IsAdminChannel = api.IsAdminChannel, + IsWatchdogChannel = api.IsWatchdogChannel + }; + /// [TgsAuthorize(ChatSettingsRights.Create)] public override async Task Create([FromBody] Api.Models.ChatSettings model, CancellationToken cancellationToken) @@ -44,10 +55,16 @@ namespace Tgstation.Server.Host.Controllers throw new ArgumentNullException(nameof(model)); if (String.IsNullOrWhiteSpace(model.Name)) - return BadRequest(new { message = "Name cannot be null or whitespace!" }); + return BadRequest(new { message = "name cannot be null or whitespace!" }); if (String.IsNullOrWhiteSpace(model.ConnectionString)) - return BadRequest(new { message = "ConnectionString cannot be null or whitespace!" }); + return BadRequest(new { message = "connection_string cannot be null or whitespace!" }); + + if (!model.Provider.HasValue) + return BadRequest(new { message = "provider cannot be null!" }); + + if (!model.Enabled.HasValue) + return BadRequest(new { message = "enabled cannot be null!" }); //try to update das db first var dbModel = new Models.ChatSettings @@ -55,13 +72,7 @@ namespace Tgstation.Server.Host.Controllers Name = model.Name, ConnectionString = model.ConnectionString, Enabled = model.Enabled, - Channels = model.Channels?.Select(x => new Models.ChatChannel - { - DiscordChannelId = x.DiscordChannelId, - IrcChannel = x.IrcChannel, - IsAdminChannel = x.IsAdminChannel, - IsWatchdogChannel = x.IsWatchdogChannel - }).ToList() ?? new List(), + Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List(), InstanceId = Instance.Id, Provider = model.Provider, }; @@ -124,5 +135,70 @@ namespace Tgstation.Server.Host.Controllers return Json(results); } + + /// + [TgsAuthorize(ChatSettingsRights.WriteChannels | ChatSettingsRights.WriteConnectionString | ChatSettingsRights.WriteEnabled | ChatSettingsRights.WriteName | ChatSettingsRights.WriteProvider)] + public override async Task Update([FromBody] Api.Models.ChatSettings model, CancellationToken cancellationToken) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + + var query = DatabaseContext.ChatSettings.Where(x => x.InstanceId == Instance.Id && x.Id == model.Id).Include(x => x.Channels); + + var current = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + if (current == default) + return StatusCode((int)HttpStatusCode.Gone); + + var userRights = (ChatSettingsRights)AuthenticationContext.GetRight(RightsType.ChatSettings); + + bool anySettingsModified = false; + + bool CheckModified(Expression> expression, ChatSettingsRights requiredRight) + { + var memberSelectorExpression = (MemberExpression)expression.Body; + var property = (PropertyInfo)memberSelectorExpression.Member; + + var newVal = property.GetValue(model); + if (newVal == null) + return false; + if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal) + return true; + + property.SetValue(current, newVal); + anySettingsModified = true; + return false; + }; + + if (!CheckModified(x => x.ConnectionString, ChatSettingsRights.WriteConnectionString) + || !CheckModified(x => x.Enabled, ChatSettingsRights.WriteEnabled) + || !CheckModified(x => x.Name, ChatSettingsRights.WriteName) + || !CheckModified(x => x.Provider, ChatSettingsRights.WriteProvider) + || (model.Channels != null && !userRights.HasFlag(ChatSettingsRights.WriteChannels))) + return Forbid(); + + if (model.Channels != null) + { + DatabaseContext.ChatChannels.RemoveRange(current.Channels); + var dbChannels = model.Channels.Select(x => ConvertApiChatChannel(x)).ToList(); + DatabaseContext.ChatChannels.AddRange(dbChannels); + current.Channels = dbChannels; + } + + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + + var chat = instanceManager.GetInstance(Instance).Chat; + + if (anySettingsModified) + //have to rebuild the thing first + await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false); + + if (model.Channels != null) + await chat.ChangeChannels(current.Id, current.Channels, cancellationToken).ConfigureAwait(false); + + if(userRights.HasFlag(ChatSettingsRights.Read)) + return Json(current); + return Ok(); + } } } From 4298c81d070f1f4452c483f1223f0a5770975738 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 16 Jul 2018 17:49:54 -0400 Subject: [PATCH 222/397] Fix the shit --- src/DMAPI/tgs/v4/api.dm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index cce546d7c4..68c95bec92 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -206,8 +206,7 @@ var/datum/tgs_chat_channel/channel = new channel.id = I["id"] channel.friendly_name = I["friendly_name"] - channel.server_name = I["server_name"] - channel.provider_name = I["provider_name"] + channel.connection_name = I["connection_name"] channel.is_admin_channel = I["is_admin_channel"] channel.is_private_channel = FALSE //tgs will never send us pm channels . += channel From dc0e4f123202218fbe73d301feacf96b4e6553ed Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 16 Jul 2018 19:45:08 -0400 Subject: [PATCH 223/397] Unify this shit --- src/DMAPI/tgs/v4/api.dm | 17 ++++++++++------- src/DMAPI/tgs/v4/commands.dm | 9 +-------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 68c95bec92..a6700ab4e5 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -203,13 +203,16 @@ //no caching cause tgs may change this var/list/json = json_decode(file2text(chat_channels_json_path)) for(var/I in json) - var/datum/tgs_chat_channel/channel = new - channel.id = I["id"] - channel.friendly_name = I["friendly_name"] - channel.connection_name = I["connection_name"] - channel.is_admin_channel = I["is_admin_channel"] - channel.is_private_channel = FALSE //tgs will never send us pm channels - . += channel + . += DecodeChannel(I) + +/datum/tgs_api/v4/proc/DecodeChannel(channel_json) + var/datum/tgs_chat_channel/channel = new + channel.id = channel_json["id"] + channel.friendly_name = channel_json["friendly_name"] + channel.connection_name = channel_json["connection_name"] + channel.is_admin_channel = channel_json["is_admin_channel"] + channel.is_admin_channel = channel_json["is_private_channel"] || FALSE + return channel #undef TGS4_TOPIC_COMMAND #undef TGS4_TOPIC_TOKEN diff --git a/src/DMAPI/tgs/v4/commands.dm b/src/DMAPI/tgs/v4/commands.dm index 6034affe19..48ca6e98de 100644 --- a/src/DMAPI/tgs/v4/commands.dm +++ b/src/DMAPI/tgs/v4/commands.dm @@ -30,14 +30,7 @@ u.id = user["id"] u.friendly_name = user["friendly_name"] u.mention = user["mention"] - var/datum/tgs_chat_channel/channel = new - u.channel = channel - var/channel_json = user["channel"] - channel.id = channel_json["id"] - channel.friendly_name = channel_json["friendly_name"] - channel.server_name = channel_json["server_name"] - channel.is_admin_channel = channel_json["is_admin_channel"] - channel.is_private_channel = channel_json["is_private_channel"] + u.channel = DecodeChannel(user["channel"]) var/datum/tgs_chat_command/sc = custom_commands[command] var/result = sc.Run(u, params) From beda35034242263d1919633b1d64ba9c22595658 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 17 Jul 2018 16:52:45 -0400 Subject: [PATCH 224/397] Work on the watchdog event handling actions --- .../Components/Watchdog/MonitorState.cs | 2 + .../Components/Watchdog/Watchdog.cs | 100 +++++++++++++++--- 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs index 09f24d30c5..5914cb2f50 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs @@ -5,6 +5,8 @@ public bool RebootingInactiveServer { get; set; } public bool InactiveServerHasStagedDmb { get; set; } + public bool InactiveServerCritFail { get; set; } + public MonitorAction NextAction { get; set; } public ISessionController ActiveServer { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index c64d6212c7..20523ca6d2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -199,11 +199,82 @@ namespace Tgstation.Server.Host.Components.Watchdog await toKill.SetRebootState(Components.Watchdog.RebootState.Shutdown, cancellationToken).ConfigureAwait(false); } - async Task HandlerMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState) + async Task HandlerMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState, CancellationToken cancellationToken) { logger.LogInformation("Monitor activation. Reason: {0}", activationReason); - await Task.Yield(); - throw new NotImplementedException(nameof(monitorState)); + switch (activationReason) + { + case MonitorActivationReason.ActiveServerCrashed: + if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail) + { + logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting"); + monitorState.NextAction = MonitorAction.Restart; + break; + } + + var dasDmbTask = dmbFactory.LockNextDmb(cancellationToken); + var result = await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); + + if (!result) + { + logger.LogWarning("Failed to activate inactive server! Restarting monitor..."); + monitorState.NextAction = MonitorAction.Restart; + break; + } + + monitorState.InactiveServerHasStagedDmb = false; + LastLaunchParameters = ActiveLaunchParameters; + + var deadServer = monitorState.ActiveServer; + monitorState.ActiveServer = monitorState.InactiveServer; + monitorState.NextAction = MonitorAction.Continue; + + try + { + monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, await dasDmbTask.ConfigureAwait(false), null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + logger.LogError("Exception occurred while recreating crashed server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString()); + //ahh jeez, what do we do here? + //this is our fault, so it should never happen + //try to start it using the active server's dmb as a backup + try + { + var dmbBackup = dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob); + monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e2) + { + //fuuuuucckkk + logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! This Exception: {0}", e2.ToString()); + monitorState.InactiveServerCritFail = true; + break; + } + } + + logger.LogInformation("Successfully relaunched inactive server!"); + monitorState.RebootingInactiveServer = true; + break; + case MonitorActivationReason.InactiveServerCrashed: + throw new NotImplementedException(); + case MonitorActivationReason.ActiveServerRebooted: + throw new NotImplementedException(); + case MonitorActivationReason.InactiveServerRebooted: + throw new NotImplementedException(); + case MonitorActivationReason.InactiveServerStartupComplete: + throw new NotImplementedException(); + case MonitorActivationReason.NewDmbAvailable: + throw new NotImplementedException(); + } } /// @@ -254,7 +325,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { MonitorActivationReason activationReason = default; //multiple things may have happened, handle them one at a time - for (var moreActivationsToProcess = true; moreActivationsToProcess && state.NextAction == MonitorAction.Continue; await HandlerMonitorWakeup(activationReason, state).ConfigureAwait(false)) + for (var moreActivationsToProcess = true; moreActivationsToProcess && state.NextAction == MonitorAction.Continue; ) { if (activeServerLifetime?.IsCompleted == true) { @@ -289,14 +360,17 @@ namespace Tgstation.Server.Host.Components.Watchdog else moreActivationsToProcess = false; } - //full reboot required - if (state.NextAction == MonitorAction.Restart) - { - logger.LogDebug("Next state action is to restart"); - DisposeAndNullControllers(); - Running = false; - chatTask = chat.SendWatchdogMessage("Restarting due to complications...", cancellationToken); - } + + await HandlerMonitorWakeup(activationReason, state, cancellationToken).ConfigureAwait(false); + } + + //full reboot required + if (state.NextAction == MonitorAction.Restart) + { + logger.LogDebug("Next state action is to restart"); + DisposeAndNullControllers(); + Running = false; + chatTask = chat.SendWatchdogMessage("Restarting due to complications...", cancellationToken); } for (var retryAttempts = 1; state.NextAction == MonitorAction.Restart; ++retryAttempts) @@ -307,7 +381,7 @@ namespace Tgstation.Server.Host.Components.Watchdog await chatTask.ConfigureAwait(false); if (Running) - state.NextAction = MonitorAction.Continue; + state = new MonitorState(); //clean the slate else { logger.LogWarning("Failed to automatically restart the watchdog! Alpha: {0}; Bravo: {1}", result.Alpha.ToString(), result.Bravo.ToString()); From 2ba4002dd78251a08e68263bdf35b105beeff963 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 17 Jul 2018 17:15:23 -0400 Subject: [PATCH 225/397] InactiveServerStartupComplete --- .../Components/Watchdog/Watchdog.cs | 91 ++++++++++--------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 20523ca6d2..dede7bdd79 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -205,64 +205,66 @@ namespace Tgstation.Server.Host.Components.Watchdog switch (activationReason) { case MonitorActivationReason.ActiveServerCrashed: - if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail) + using (monitorState.ActiveServer) //it's dead, dispose it when we're done { - logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting"); - monitorState.NextAction = MonitorAction.Restart; - break; - } + if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail) + { + logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting"); + monitorState.NextAction = MonitorAction.Restart; + break; + } - var dasDmbTask = dmbFactory.LockNextDmb(cancellationToken); - var result = await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); + var dasDmbTask = dmbFactory.LockNextDmb(cancellationToken); + var result = await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); - if (!result) - { - logger.LogWarning("Failed to activate inactive server! Restarting monitor..."); - monitorState.NextAction = MonitorAction.Restart; - break; - } + if (!result) + { + logger.LogWarning("Failed to activate inactive server! Restarting monitor..."); + monitorState.NextAction = MonitorAction.Restart; + break; + } - monitorState.InactiveServerHasStagedDmb = false; - LastLaunchParameters = ActiveLaunchParameters; + monitorState.InactiveServerHasStagedDmb = false; + LastLaunchParameters = ActiveLaunchParameters; - var deadServer = monitorState.ActiveServer; - monitorState.ActiveServer = monitorState.InactiveServer; - monitorState.NextAction = MonitorAction.Continue; + monitorState.ActiveServer = monitorState.InactiveServer; + monitorState.NextAction = MonitorAction.Continue; - try - { - monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, await dasDmbTask.ConfigureAwait(false), null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) - { - logger.LogError("Exception occurred while recreating crashed server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString()); - //ahh jeez, what do we do here? - //this is our fault, so it should never happen - //try to start it using the active server's dmb as a backup try { - var dmbBackup = dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob); - monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); + monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, await dasDmbTask.ConfigureAwait(false), null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } - catch (Exception e2) + catch (Exception e) { - //fuuuuucckkk - logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! This Exception: {0}", e2.ToString()); - monitorState.InactiveServerCritFail = true; - break; + logger.LogError("Exception occurred while recreating crashed server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString()); + //ahh jeez, what do we do here? + //this is our fault, so it should never happen + //try to start it using the active server's dmb as a backup + try + { + var dmbBackup = dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob); + monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e2) + { + //fuuuuucckkk + logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! This Exception: {0}", e2.ToString()); + monitorState.InactiveServerCritFail = true; + break; + } } - } - logger.LogInformation("Successfully relaunched inactive server!"); - monitorState.RebootingInactiveServer = true; + logger.LogInformation("Successfully relaunched inactive server!"); + monitorState.RebootingInactiveServer = true; + } break; case MonitorActivationReason.InactiveServerCrashed: throw new NotImplementedException(); @@ -271,7 +273,10 @@ namespace Tgstation.Server.Host.Components.Watchdog case MonitorActivationReason.InactiveServerRebooted: throw new NotImplementedException(); case MonitorActivationReason.InactiveServerStartupComplete: - throw new NotImplementedException(); + //eziest case of my life + monitorState.RebootingInactiveServer = false; + monitorState.NextAction = MonitorAction.Continue; + break; case MonitorActivationReason.NewDmbAvailable: throw new NotImplementedException(); } From 94729b10bab6c959d0ff91816d8824f56c01e82e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 10:56:17 -0400 Subject: [PATCH 226/397] Many more watch monitor cases --- .../Components/Watchdog/MonitorAction.cs | 1 - .../Components/Watchdog/MonitorState.cs | 1 + .../Components/Watchdog/Watchdog.cs | 266 ++++++++++++++---- 3 files changed, 209 insertions(+), 59 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs index 3140b9e854..b5178c7b55 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs @@ -6,7 +6,6 @@ enum MonitorAction { Continue, - Break, Restart, Exit } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs index 5914cb2f50..367c2c3e80 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs @@ -3,6 +3,7 @@ sealed class MonitorState { public bool RebootingInactiveServer { get; set; } + public bool InactiveServerHasStagedDmb { get; set; } public bool InactiveServerCritFail { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index dede7bdd79..ac949b65b2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -90,10 +90,23 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly bool autoStart; + /// + /// The for the monitor loop + /// CancellationTokenSource monitorCts; + + /// + /// The running the monitor loop + /// Task monitorTask; + /// + /// Server designation alpha + /// ISessionController alphaServer; + /// + /// Server designation bravo + /// ISessionController bravoServer; /// @@ -147,6 +160,9 @@ namespace Tgstation.Server.Host.Components.Watchdog semaphore.Dispose(); } + /// + /// Call on and and set them to + /// void DisposeAndNullControllers() { logger.LogTrace("DisposeAndNullControllers"); @@ -156,6 +172,9 @@ namespace Tgstation.Server.Host.Components.Watchdog bravoServer = null; } + /// + /// Implementation of . Does not lock + /// async Task RestartNoLock(bool graceful, CancellationToken cancellationToken) { var running = Running; @@ -181,6 +200,12 @@ namespace Tgstation.Server.Host.Components.Watchdog return null; } + /// + /// Implementation of . Does not lock + /// + /// If the termination will be delayed until a reboot is detected in the active server's DMAPI and this function will return immediately + /// If the termination will be announced using + /// The for the operation async Task TerminateNoLock(bool graceful, bool announce, CancellationToken cancellationToken) { if (!Running) @@ -199,82 +224,208 @@ namespace Tgstation.Server.Host.Components.Watchdog await toKill.SetRebootState(Components.Watchdog.RebootState.Shutdown, cancellationToken).ConfigureAwait(false); } + /// + /// Handles the actions to take when the monitor has to "wake up" + /// + /// The that caused the invocation + /// The current . Will be modified upon retrn + /// The for the operation + /// A representing the running operation async Task HandlerMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState, CancellationToken cancellationToken) { logger.LogInformation("Monitor activation. Reason: {0}", activationReason); + + //returns true if the inactive server can't be used immediately + bool FullRestartDeadInactive() + { + if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail) + { + logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting"); + monitorState.NextAction = MonitorAction.Restart; //will dispose server + return true; + } + return false; + }; + + //trys to set inactive server's port to the private port + async Task MakeInactiveActive() + { + logger.LogInformation("Setting inactive server to port {0}...", ActiveLaunchParameters.PrimaryPort.Value); + var result = await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); + + if (!result) + { + logger.LogWarning("Failed to activate inactive server! Restarting monitor..."); + monitorState.NextAction = MonitorAction.Restart; //will dispose server + return false; + } + + // should always be set for InactiveServer + monitorState.InactiveServer.ClosePortOnReboot = false; + monitorState.ActiveServer.ClosePortOnReboot = true; + + //inactive server should always be using active launch parameters + LastLaunchParameters = ActiveLaunchParameters; + + var tmp = monitorState.ActiveServer; + monitorState.ActiveServer = monitorState.InactiveServer; + monitorState.InactiveServer = tmp; + return true; + } + + //trys to load inactive server with latest dmb, falling back to current dmb on failure and returning false + async Task RestartInactiveServer() + { + logger.LogInformation("Rebooting inactive server..."); + var newDmb = dmbFactory.LockNextDmb(cancellationToken); + bool usedMostRecentDmb; + try + { + monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, await newDmb.ConfigureAwait(false), null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); + usedMostRecentDmb = true; + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + logger.LogError("Exception occurred while recreating server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString()); + //ahh jeez, what do we do here? + //this is our fault, so it should never happen but + //idk maybe a database error while handling the newest dmb? + //either way try to start it using the active server's dmb as a backup + try + { + var dmbBackup = dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob); + monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); + usedMostRecentDmb = false; + await chat.SendWatchdogMessage("Staging newest DMB on inactive server failed: {0} Falling back to previous dmb...", cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e2) + { + //fuuuuucckkk + logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! This Exception: {0}", e2.ToString()); + monitorState.InactiveServerCritFail = true; + await chat.SendWatchdogMessage("Attempted reboot of inactive server failed. Watchdog will reset when active server fails or exits", cancellationToken).ConfigureAwait(false); + return true; //we didn't use the old dmb + } + } + + logger.LogInformation("Successfully relaunched inactive server!"); + monitorState.RebootingInactiveServer = true; + // should always be set for InactiveServer + monitorState.InactiveServer.ClosePortOnReboot = false; + return usedMostRecentDmb; + } + + //reason handling switch (activationReason) { case MonitorActivationReason.ActiveServerCrashed: - using (monitorState.ActiveServer) //it's dead, dispose it when we're done + if(monitorState.ActiveServer.RebootState == Components.Watchdog.RebootState.Shutdown) { - if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail) - { - logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting"); - monitorState.NextAction = MonitorAction.Restart; - break; - } + await chat.SendWatchdogMessage("Active server crashed! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false); + monitorState.NextAction = MonitorAction.Exit; + break; + } - var dasDmbTask = dmbFactory.LockNextDmb(cancellationToken); - var result = await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); + if (FullRestartDeadInactive()) + { + await chat.SendWatchdogMessage("Active server crashed! Inactive server unable to online!", cancellationToken).ConfigureAwait(false); + break; + } - if (!result) - { - logger.LogWarning("Failed to activate inactive server! Restarting monitor..."); - monitorState.NextAction = MonitorAction.Restart; - break; - } + await chat.SendWatchdogMessage("Active server crashed! Onlining inactive server...", cancellationToken).ConfigureAwait(false); + if (!await MakeInactiveActive().ConfigureAwait(false)) + break; - monitorState.InactiveServerHasStagedDmb = false; - LastLaunchParameters = ActiveLaunchParameters; - - monitorState.ActiveServer = monitorState.InactiveServer; + monitorState.NextAction = MonitorAction.Continue; + monitorState.ActiveServer.ClosePortOnReboot = false; + goto case MonitorActivationReason.InactiveServerCrashed; + case MonitorActivationReason.InactiveServerCrashed: + using (monitorState.InactiveServer) //it's dead, dispose it when we're done + { monitorState.NextAction = MonitorAction.Continue; - - try + var usedLatestDmb = await RestartInactiveServer().ConfigureAwait(false); + if (monitorState.NextAction == MonitorAction.Continue) { - monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, await dasDmbTask.ConfigureAwait(false), null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); + monitorState.ActiveServer.ClosePortOnReboot = false; + if (monitorState.InactiveServerHasStagedDmb && !usedLatestDmb) + monitorState.InactiveServerHasStagedDmb = false; //don't try to load it again though } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) - { - logger.LogError("Exception occurred while recreating crashed server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString()); - //ahh jeez, what do we do here? - //this is our fault, so it should never happen - //try to start it using the active server's dmb as a backup - try - { - var dmbBackup = dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob); - monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e2) - { - //fuuuuucckkk - logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! This Exception: {0}", e2.ToString()); - monitorState.InactiveServerCritFail = true; - break; - } - } - - logger.LogInformation("Successfully relaunched inactive server!"); - monitorState.RebootingInactiveServer = true; } break; - case MonitorActivationReason.InactiveServerCrashed: - throw new NotImplementedException(); case MonitorActivationReason.ActiveServerRebooted: - throw new NotImplementedException(); + if (FullRestartDeadInactive()) + break; + + //what matters here is the RebootState + bool restartOnceSwapped = false; + var rebootState = monitorState.ActiveServer.RebootState; + monitorState.ActiveServer.ResetRebootState(); //the DMAPI has already done this internally + + /* TODO: This should be handled when ActiveLaunchParameters is SET + + if(LastLaunchParameters != ActiveLaunchParameters && rebootState != Components.Watchdog.RebootState.Shutdown) + //they need a relaunch with active parameters + rebootState = Components.Watchdog.RebootState.Restart; + */ + + switch (rebootState) + { + case Components.Watchdog.RebootState.Normal: + break; + case Components.Watchdog.RebootState.Restart: + restartOnceSwapped = true; + break; + case Components.Watchdog.RebootState.Shutdown: + await chat.SendWatchdogMessage("Active server rebooted! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false); + DisposeAndNullControllers(); + Running = false; + monitorState.NextAction = MonitorAction.Exit; + return; + } + + if (monitorState.InactiveServerHasStagedDmb) + { + if (monitorState.InactiveServer.Dmb.CompileJob.Id == monitorState.ActiveServer.Dmb.CompileJob.Id) + //both servers up to date + monitorState.InactiveServerHasStagedDmb = false; + else + //need to load a new dmb in ActiveServer + restartOnceSwapped = true; + } + + if (!await MakeInactiveActive().ConfigureAwait(false)) + break; + + if(!restartOnceSwapped) + //try to reopen inactive server on the private port so it's not pinging all the time + //failing that, just reboot it + restartOnceSwapped = !await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false); + + if (restartOnceSwapped) //for one reason or another, + { + monitorState.InactiveServer.Dispose(); + monitorState.InactiveServerHasStagedDmb = await RestartInactiveServer().ConfigureAwait(false); + } + break; case MonitorActivationReason.InactiveServerRebooted: - throw new NotImplementedException(); + //should never happen but okay + logger.LogWarning("Inactive server rebooted, this is a bug in DM code!"); + monitorState.RebootingInactiveServer = true; + monitorState.ActiveServer.ClosePortOnReboot = false; + monitorState.NextAction = MonitorAction.Continue; + break; case MonitorActivationReason.InactiveServerStartupComplete: //eziest case of my life monitorState.RebootingInactiveServer = false; + monitorState.ActiveServer.ClosePortOnReboot = true; monitorState.NextAction = MonitorAction.Continue; break; case MonitorActivationReason.NewDmbAvailable: @@ -374,8 +525,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { logger.LogDebug("Next state action is to restart"); DisposeAndNullControllers(); - Running = false; - chatTask = chat.SendWatchdogMessage("Restarting due to complications...", cancellationToken); + chatTask = chat.SendWatchdogMessage("Restarting entirely due to complications...", cancellationToken); } for (var retryAttempts = 1; state.NextAction == MonitorAction.Restart; ++retryAttempts) From 4af3bd811ee87b60302a70e44e6cf84e78e511cf Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 10:58:02 -0400 Subject: [PATCH 227/397] Implement ISessionController.ResetRebootState() --- .../Components/Watchdog/ISessionController.cs | 7 ++++++- .../Components/Watchdog/SessionController.cs | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs index faa997cbf1..6624d03145 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionController.cs @@ -79,5 +79,10 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The for the operation /// A resulting in if the operation succeeded, otherwise Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken); - } + + /// + /// Changes to without telling the DMAPI + /// + void ResetRebootState(); + } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 4937f7e170..757734d4f1 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -333,5 +333,12 @@ namespace Tgstation.Server.Host.Components.Watchdog return await SendCommand(String.Format(CultureInfo.InvariantCulture, "{0}&{1}={2}", InteropConstants.DMTopicChangeReboot, InteropConstants.DMParameterNewRebootMode, (int)newRebootState), cancellationToken).ConfigureAwait(false) == InteropConstants.DMResponseSuccess; } + + /// + public void ResetRebootState() + { + CheckDisposed(); + reattachInformation.RebootState = RebootState.Normal; + } } } From e467ac457f4c82285e6d3b5dbfb64450d35c955f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 11:18:03 -0400 Subject: [PATCH 228/397] Good shit --- .../Watchdog/MonitorActivationReason.cs | 3 +- .../Components/Watchdog/Watchdog.cs | 83 ++++++++++++------- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs index 1c36c6f0d6..45349a4be0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs @@ -7,6 +7,7 @@ ActiveServerRebooted, InactiveServerRebooted, NewDmbAvailable, - InactiveServerStartupComplete + InactiveServerStartupComplete, + ActiveLaunchParametersUpdated } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index ac949b65b2..9d060f4063 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -100,6 +100,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// Task monitorTask; + /// + /// that completes when are changed and we are + /// + TaskCompletionSource activeParametersUpdated; + /// /// Server designation alpha /// @@ -151,6 +156,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ActiveLaunchParameters = initialLaunchParameters; releaseServers = false; semaphore = new SemaphoreSlim(1); + activeParametersUpdated = new TaskCompletionSource(); } /// @@ -198,7 +204,7 @@ namespace Tgstation.Server.Host.Components.Watchdog //todo, log the result await toReboot.SetRebootState(Components.Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false); return null; - } + } /// /// Implementation of . Does not lock @@ -270,10 +276,11 @@ namespace Tgstation.Server.Host.Components.Watchdog var tmp = monitorState.ActiveServer; monitorState.ActiveServer = monitorState.InactiveServer; monitorState.InactiveServer = tmp; + AlphaIsActive = !AlphaIsActive; return true; } - //trys to load inactive server with latest dmb, falling back to current dmb on failure and returning false + // Tries to load inactive server with latest dmb, falling back to current dmb on failure. Requires a lock on async Task RestartInactiveServer() { logger.LogInformation("Rebooting inactive server..."); @@ -312,7 +319,7 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! This Exception: {0}", e2.ToString()); monitorState.InactiveServerCritFail = true; await chat.SendWatchdogMessage("Attempted reboot of inactive server failed. Watchdog will reset when active server fails or exits", cancellationToken).ConfigureAwait(false); - return true; //we didn't use the old dmb + return true; //we didn't use the old dmb } } @@ -329,25 +336,31 @@ namespace Tgstation.Server.Host.Components.Watchdog case MonitorActivationReason.ActiveServerCrashed: if(monitorState.ActiveServer.RebootState == Components.Watchdog.RebootState.Shutdown) { - await chat.SendWatchdogMessage("Active server crashed! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false); + await chat.SendWatchdogMessage("Active server crashed or exited! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false); monitorState.NextAction = MonitorAction.Exit; break; } if (FullRestartDeadInactive()) { - await chat.SendWatchdogMessage("Active server crashed! Inactive server unable to online!", cancellationToken).ConfigureAwait(false); + await chat.SendWatchdogMessage("Active server crashed or exited! Inactive server unable to online!", cancellationToken).ConfigureAwait(false); break; } - await chat.SendWatchdogMessage("Active server crashed! Onlining inactive server...", cancellationToken).ConfigureAwait(false); + await chat.SendWatchdogMessage("Active server crashed or exited! Onlining inactive server...", cancellationToken).ConfigureAwait(false); if (!await MakeInactiveActive().ConfigureAwait(false)) break; monitorState.NextAction = MonitorAction.Continue; monitorState.ActiveServer.ClosePortOnReboot = false; - goto case MonitorActivationReason.InactiveServerCrashed; + goto case MonitorActivationReason.ActiveLaunchParametersUpdated; case MonitorActivationReason.InactiveServerCrashed: + await chat.SendWatchdogMessage("Inactive server crashed or exited! Rebooting...", cancellationToken).ConfigureAwait(false); + goto case MonitorActivationReason.ActiveLaunchParametersUpdated; + case MonitorActivationReason.ActiveLaunchParametersUpdated: + //replace the notification tcs here so that the next loop will read a fresh one + activeParametersUpdated = new TaskCompletionSource(); + using (monitorState.InactiveServer) //it's dead, dispose it when we're done { monitorState.NextAction = MonitorAction.Continue; @@ -442,7 +455,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { logger.LogDebug("Entered MonitorLifetimes"); var iteration = 1; - for(var state = new MonitorState(); state.NextAction != MonitorAction.Exit; ++iteration) + for(var monitorState = new MonitorState(); monitorState.NextAction != MonitorAction.Exit; ++iteration) { logger.LogDebug("New iteration of monitor loop"); try @@ -452,26 +465,27 @@ namespace Tgstation.Server.Host.Components.Watchdog else logger.LogDebug("Bravo is the active server"); - if(state.InactiveServerHasStagedDmb) + if(monitorState.InactiveServerHasStagedDmb) logger.LogDebug("Inactive server has staged .dmb"); - if (state.RebootingInactiveServer) + if (monitorState.RebootingInactiveServer) logger.LogDebug("Inactive server is rebooting"); - state.ActiveServer = AlphaIsActive ? alphaServer : bravoServer; - state.InactiveServer = AlphaIsActive ? bravoServer : alphaServer; + monitorState.ActiveServer = AlphaIsActive ? alphaServer : bravoServer; + monitorState.InactiveServer = AlphaIsActive ? bravoServer : alphaServer; - var activeServerLifetime = state.ActiveServer.Lifetime; - var inactiveServerLifetime = state.InactiveServer.Lifetime; - var activeServerReboot = state.ActiveServer.OnReboot; - var inactiveServerReboot = state.InactiveServer.OnReboot; - var inactiveServerStartup = state.InactiveServer.LaunchResult; + var activeServerLifetime = monitorState.ActiveServer.Lifetime; + var inactiveServerLifetime = monitorState.InactiveServer.Lifetime; + var activeServerReboot = monitorState.ActiveServer.OnReboot; + var inactiveServerReboot = monitorState.InactiveServer.OnReboot; + var inactiveServerStartup = monitorState.InactiveServer.LaunchResult; + var activeLaunchParametersChanged = activeParametersUpdated.Task; var newDmbAvailable = dmbFactory.OnNewerDmb; var cancelTcs = new TaskCompletionSource(); using (cancellationToken.Register(() => cancelTcs.SetCanceled())) { - var toWaitOn = Task.WhenAny(activeServerLifetime, inactiveServerLifetime, activeServerReboot, inactiveServerReboot, newDmbAvailable, cancelTcs.Task); - if (state.RebootingInactiveServer) + var toWaitOn = Task.WhenAny(activeServerLifetime, inactiveServerLifetime, activeServerReboot, inactiveServerReboot, newDmbAvailable, cancelTcs.Task, activeLaunchParametersChanged); + if (monitorState.RebootingInactiveServer) toWaitOn = Task.WhenAny(toWaitOn, inactiveServerStartup); await toWaitOn.ConfigureAwait(false); } @@ -481,7 +495,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { MonitorActivationReason activationReason = default; //multiple things may have happened, handle them one at a time - for (var moreActivationsToProcess = true; moreActivationsToProcess && state.NextAction == MonitorAction.Continue; ) + for (var moreActivationsToProcess = true; moreActivationsToProcess && monitorState.NextAction == MonitorAction.Continue; ) { if (activeServerLifetime?.IsCompleted == true) { @@ -513,31 +527,41 @@ namespace Tgstation.Server.Host.Components.Watchdog activationReason = MonitorActivationReason.NewDmbAvailable; newDmbAvailable = null; } + else if(activeLaunchParametersChanged?.IsCompleted == true) + { + activationReason = MonitorActivationReason.ActiveLaunchParametersUpdated; + activeLaunchParametersChanged = null; + } else moreActivationsToProcess = false; } - await HandlerMonitorWakeup(activationReason, state, cancellationToken).ConfigureAwait(false); + await HandlerMonitorWakeup(activationReason, monitorState, cancellationToken).ConfigureAwait(false); + //writeback alphaServer and bravoServer + alphaServer = AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer; + bravoServer = AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer; } //full reboot required - if (state.NextAction == MonitorAction.Restart) + if (monitorState.NextAction == MonitorAction.Restart) { logger.LogDebug("Next state action is to restart"); DisposeAndNullControllers(); chatTask = chat.SendWatchdogMessage("Restarting entirely due to complications...", cancellationToken); } - for (var retryAttempts = 1; state.NextAction == MonitorAction.Restart; ++retryAttempts) + for (var retryAttempts = 1; monitorState.NextAction == MonitorAction.Restart; ++retryAttempts) { WatchdogLaunchResult result; using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { result = await LaunchNoLock(false, false, false, cancellationToken).ConfigureAwait(false); + if (Running) + monitorState = new MonitorState(); //clean the slate + } - await chatTask.ConfigureAwait(false); - if (Running) - state = new MonitorState(); //clean the slate - else + await chatTask.ConfigureAwait(false); + if(!Running) { logger.LogWarning("Failed to automatically restart the watchdog! Alpha: {0}; Bravo: {1}", result.Alpha.ToString(), result.Bravo.ToString()); var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); //max of one hour @@ -553,7 +577,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch (Exception e) { - logger.LogError("Monitor crashed! Iteration: {0}, State: {1}", iteration, JsonConvert.SerializeObject(state)); + logger.LogError("Monitor crashed! Iteration: {0}, State: {1}", iteration, JsonConvert.SerializeObject(monitorState)); await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Monitor crashed, this should NEVER happen! Please report this, full details in logs! Restarting monitor... Error: {0}", e.Message), cancellationToken).ConfigureAwait(false); } } @@ -579,7 +603,8 @@ namespace Tgstation.Server.Host.Components.Watchdog { ActiveLaunchParameters = launchParameters; if (Running) - await RestartNoLock(true, cancellationToken).ConfigureAwait(false); + //queue an update + activeParametersUpdated.TrySetResult(null); } } From be64ee486785e2f63e79f158f375ade22a642952 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 11:57:35 -0400 Subject: [PATCH 229/397] Implement DiscordProvider and other things --- src/DMAPI/tgs.dm | 2 +- .../Models/ChatChannel.cs | 2 +- .../Components/Chat/Channel.cs | 2 +- .../Components/Chat/ChannelMapping.cs | 2 +- .../Components/Chat/Chat.cs | 16 +- .../Components/Chat/IChat.cs | 2 +- .../Components/Chat/Message.cs | 4 +- .../Chat/Providers/DiscordProvider.cs | 248 ++++++++++++++++++ .../Components/Chat/Providers/IProvider.cs | 7 +- .../Components/Chat/Response.cs | 2 +- .../Components/Chat/User.cs | 25 +- 11 files changed, 290 insertions(+), 22 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index c3daf1a380..628b17cac6 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -103,7 +103,7 @@ //represents a chat user /datum/tgs_chat_user - var/id //Internal user representation + var/id //Internal user representation, requires channel to be unique var/friendly_name //The user's public name var/mention //The text to use to ping this user in a message var/datum/tgs_chat_channel/channel //The /datum/tgs_chat_channel this user was from diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index 942154e506..d5131f4ff1 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -13,7 +13,7 @@ /// /// The Discord channel ID /// - public long? DiscordChannelId { get; set; } + public ulong? DiscordChannelId { get; set; } /// /// If the is an admin channel diff --git a/src/Tgstation.Server.Host/Components/Chat/Channel.cs b/src/Tgstation.Server.Host/Components/Chat/Channel.cs index c2eab84dd8..b29dc5c90c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Channel.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Channel.cs @@ -9,7 +9,7 @@ /// The channel Id. /// /// remaps this to an internal id using - public long Id { get; set; } + public ulong Id { get; set; } /// /// The user friendly name of the diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs index 243bf71206..a1ed4dd808 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs @@ -3,7 +3,7 @@ sealed class ChannelMapping { public long ProviderId { get; set; } - public long ProviderChannelId { get; set; } + public ulong ProviderChannelId { get; set; } public bool IsWatchdogChannel { get; set; } public Channel Channel { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 07a29865c9..82a8989940 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -13,6 +13,8 @@ namespace Tgstation.Server.Host.Components.Chat /// sealed class Chat : IChat { + const string CommonMention = "!tgs"; + /// /// The for the /// @@ -36,7 +38,7 @@ namespace Tgstation.Server.Host.Components.Chat /// /// Map of s to s /// - readonly Dictionary mappedChannels; + readonly Dictionary mappedChannels; /// /// The active s for the @@ -51,7 +53,7 @@ namespace Tgstation.Server.Host.Components.Chat /// /// Used for remapping s /// - long channelIdCounter; + ulong channelIdCounter; /// /// If has been called @@ -71,7 +73,7 @@ namespace Tgstation.Server.Host.Components.Chat builtinCommands = commandFactory?.GenerateCommands() ?? throw new ArgumentNullException(nameof(commandFactory)); providers = new Dictionary(); - mappedChannels = new Dictionary(); + mappedChannels = new Dictionary(); trackingContexts = new List(); channelIdCounter = 1; } @@ -131,11 +133,11 @@ namespace Tgstation.Server.Host.Components.Chat Channel = y }); - long baseId; + ulong baseId; lock (this) { baseId = channelIdCounter; - channelIdCounter += results.Count; + channelIdCounter += (ulong)results.Count; } Task task; @@ -185,7 +187,7 @@ namespace Tgstation.Server.Host.Components.Chat } /// - public Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken) + public Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken) { if (message == null) throw new ArgumentNullException(nameof(message)); @@ -209,7 +211,7 @@ namespace Tgstation.Server.Host.Components.Chat /// public Task SendWatchdogMessage(string message, CancellationToken cancellationToken) { - List wdChannels; + List wdChannels; 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); diff --git a/src/Tgstation.Server.Host/Components/Chat/IChat.cs b/src/Tgstation.Server.Host/Components/Chat/IChat.cs index 68c12aa1ed..15e6e939bc 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChat.cs @@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Components.Chat /// The s of the s to send to /// The for the operation /// A representing the running operation - Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken); + Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken); /// /// Send a chat to configured watchdog channels diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs index e9b90e6560..34ba645541 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Message.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs @@ -2,7 +2,7 @@ { sealed class Message { - string Content { get; set; } - User User { get; set; } + public string Content { get; set; } + public User User { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs new file mode 100644 index 0000000000..367973b919 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -0,0 +1,248 @@ +using Discord; +using Discord.Net; +using Discord.WebSocket; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Components.Chat.Providers +{ + /// + /// for the Discord app + /// + sealed class DiscordProvider : IProvider + { + /// + public bool Connected { get; private set; } + + /// + public string BotMention + { + get + { + if (!Connected) + throw new InvalidOperationException("Provider not connected"); + return client.CurrentUser.Mention; + } + } + + readonly ILogger logger; + + /// + /// The for the + /// + readonly DiscordSocketClient client; + + /// + /// The name used for populating + /// + readonly string connectionName; + + /// + /// The token used for connecting to discord + /// + readonly string botToken; + + /// + /// of received s + /// + readonly Queue messageQueue; + + /// + /// of mapped s + /// + readonly List mappedChannels; + + /// + /// that completes while isn't empty + /// + TaskCompletionSource nextMessage; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + public DiscordProvider(ILogger logger, string connectionName, string botToken) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.connectionName = connectionName ?? throw new ArgumentNullException(nameof(connectionName)); + this.botToken = botToken ?? throw new ArgumentNullException(nameof(botToken)); + client = new DiscordSocketClient(); + client.MessageReceived += Client_MessageReceived; + nextMessage = new TaskCompletionSource(); + mappedChannels = new List(); + messageQueue = new Queue(); + } + + /// + public void Dispose() => client.Dispose(); + + /// + /// Handle a message recieved from Discord + /// + /// The + /// A representing the running operation + Task Client_MessageReceived(SocketMessage e) + { + if (e.Author.Id != client.CurrentUser.Id) + return Task.CompletedTask; + + var pm = e.Channel is IPrivateChannel; + + if (!pm && !mappedChannels.Contains(e.Channel.Id)) + return Task.CompletedTask; + + var result = new Message { + Content = e.Content, + User = new User + { + Id = e.Author.Id, + Channel = new Channel + { + Id = e.Channel.Id, + IsAdmin = false, + IsPrivate = true, + ConnectionName = connectionName, + FriendlyName = e.Channel.Name + }, + FriendlyName = e.Author.Username, + Mention = e.Author.Mention + } + }; + + lock (this) + { + messageQueue.Enqueue(result); + nextMessage.TrySetResult(null); + } + return Task.CompletedTask; + } + + /// + public async Task NextMessage(CancellationToken cancellationToken) + { + var cancelTcs = new TaskCompletionSource(); + using (cancellationToken.Register(() => cancelTcs.SetCanceled())) + await Task.WhenAny(nextMessage.Task, cancelTcs.Task).ConfigureAwait(false); + lock (this) + { + var result = messageQueue.Dequeue(); + if (messageQueue.Count == 0) + nextMessage = new TaskCompletionSource(); + return result; + } + } + + /// + public async Task Connect(CancellationToken cancellationToken) + { + if (Connected) + return true; + + try + { + await client.LoginAsync(TokenType.Bot, botToken, true).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + await client.StartAsync().ConfigureAwait(false); + + var channelsAvailable = new TaskCompletionSource(); + client.Ready += () => + { + channelsAvailable.SetResult(null); + return Task.CompletedTask; + }; + using (cancellationToken.Register(() => channelsAvailable.SetCanceled())) + await channelsAvailable.Task.ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning("Error connecting to Discord: {0}", e); + return false; + } + + Connected = true; + return true; + } + + public async Task Disconnect(CancellationToken cancellationToken) + { + if (!Connected) + return; + + try + { + await client.StopAsync().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + await client.LogoutAsync().ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning("Error disconnecting from discord: {0}", e); + } + Connected = false; + } + + /// + public Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) + { + if (channels == null) + throw new ArgumentNullException(nameof(channels)); + + if (!Connected) + throw new InvalidOperationException("Provider not connected!"); + + Channel GetChannelForChatChannel(ChatChannel channel) + { + if (!channel.DiscordChannelId.HasValue) + throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); + + var discordChannel = client.GetChannel(channel.DiscordChannelId.Value); + + if (discordChannel == null) + return null; + + return new Channel + { + Id = discordChannel.Id, + IsAdmin = channel.IsAdminChannel, + ConnectionName = connectionName, + FriendlyName = (discordChannel as ITextChannel)?.Name ?? "UNKNOWN", + IsPrivate = false + }; + }; + + var enumerator = channels.Select(x => GetChannelForChatChannel(x)).Where(x => x != null); + + lock (this) + { + mappedChannels.Clear(); + mappedChannels.AddRange(enumerator.Select(x => x.Id)); + } + + return Task.FromResult>(enumerator.ToList()); + } + + /// + public async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) { + try + { + await ((client.GetChannel(channelId) as ITextChannel)?.SendMessageAsync(message, false, null, new RequestOptions + { + CancelToken = cancellationToken + }) ?? Task.CompletedTask).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning("Error sending discord message: {0}", e); + } + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs index 7abe357269..5b6761ad2c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -23,7 +23,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// Get a resulting in the next the recieves or on a disconnect /// - Task NextMessage { get; } + /// The for the operation + /// A resulting in the next available + /// Note that private messages will come in the form of s not returned in + Task NextMessage(CancellationToken cancellationToken); /// /// Attempt to connect the @@ -54,6 +57,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// The message contents /// The for the operation /// A representing the running operation - Task SendMessage(long channelId, string message, CancellationToken cancellationToken); + Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Response.cs b/src/Tgstation.Server.Host/Components/Chat/Response.cs index 855752e790..59be02b4ba 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Response.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Response.cs @@ -15,6 +15,6 @@ namespace Tgstation.Server.Host.Components.Chat /// /// The list of internal channel ids to send to /// - public List ChannelIds { get; set; } + public List ChannelIds { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/User.cs b/src/Tgstation.Server.Host/Components/Chat/User.cs index 12eff58285..6b7cbda2ab 100644 --- a/src/Tgstation.Server.Host/Components/Chat/User.cs +++ b/src/Tgstation.Server.Host/Components/Chat/User.cs @@ -1,13 +1,28 @@ namespace Tgstation.Server.Host.Components.Chat { /// - /// + /// Represents a tgs_chat_user datum /// public sealed class User { - long Id { get; set; } - string FriendlyName { get; set; } - string Mention { get; set; } - Channel Channel { get; set; } + /// + /// The internal user id + /// + public ulong Id { get; set; } + + /// + /// The friendly name of the user + /// + public string FriendlyName { get; set; } + + /// + /// The text to mention the user + /// + public string Mention { get; set; } + + /// + /// The the user spoke from + /// + public Channel Channel { get; set; } } } From 178a822546a1529a5514cc9da7710c5a04062ba7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 11:59:48 -0400 Subject: [PATCH 230/397] Minor doc update --- .../Components/Chat/Providers/DiscordProvider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 367973b919..f78d320d00 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -1,5 +1,4 @@ using Discord; -using Discord.Net; using Discord.WebSocket; using Microsoft.Extensions.Logging; using System; @@ -30,6 +29,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } } + /// + /// The for the + /// readonly ILogger logger; /// From ae66f888952159399d403a0d5a681d2a411dd360 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 12:13:01 -0400 Subject: [PATCH 231/397] Convert user and channel ids to string backing fields --- .../Components/Chat/Channel.cs | 18 ++++++++++++++++-- .../Components/Chat/Chat.cs | 4 ++-- .../Components/Chat/ICommandFactory.cs | 6 +++--- .../Chat/Providers/DiscordProvider.cs | 8 ++++---- .../Components/Chat/User.cs | 18 ++++++++++++++++-- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Channel.cs b/src/Tgstation.Server.Host/Components/Chat/Channel.cs index b29dc5c90c..07111826d8 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Channel.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Channel.cs @@ -1,15 +1,29 @@ -namespace Tgstation.Server.Host.Components.Chat +using Newtonsoft.Json; +using System; +using System.Globalization; + +namespace Tgstation.Server.Host.Components.Chat { /// /// Represents a channel /// public sealed class Channel { + /// + /// Backing field for . Represented as a to avoid BYOND percision loss + /// + public string Id { get; set; } + /// /// The channel Id. /// /// remaps this to an internal id using - public ulong Id { get; set; } + [JsonIgnore] + public ulong RealId + { + get => UInt64.Parse(Id, CultureInfo.InvariantCulture); + set => Id = value.ToString(CultureInfo.InvariantCulture); + } /// /// The user friendly name of the diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 82a8989940..fb9b656ef0 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -128,7 +128,7 @@ namespace Tgstation.Server.Host.Components.Chat var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping { IsWatchdogChannel = x.IsWatchdogChannel, - ProviderChannelId = y.Id, + ProviderChannelId = y.RealId, ProviderId = connectionId, Channel = y }); @@ -150,7 +150,7 @@ namespace Tgstation.Server.Host.Components.Chat { var newId = baseId++; mappedChannels.Add(newId, I); - I.Channel.Id = newId; + I.Channel.RealId = newId; } lock (trackingContexts) diff --git a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs index 4020ab0613..8abfe7abcd 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs @@ -3,8 +3,8 @@ using Tgstation.Server.Host.Components.Chat.Commands; namespace Tgstation.Server.Host.Components.Chat { - interface ICommandFactory - { + interface ICommandFactory + { IReadOnlyList GenerateCommands(); - } + } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index f78d320d00..2f3387b821 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -104,10 +104,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Content = e.Content, User = new User { - Id = e.Author.Id, + RealId = e.Author.Id, Channel = new Channel { - Id = e.Channel.Id, + RealId = e.Channel.Id, IsAdmin = false, IsPrivate = true, ConnectionName = connectionName, @@ -213,7 +213,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return new Channel { - Id = discordChannel.Id, + RealId = discordChannel.Id, IsAdmin = channel.IsAdminChannel, ConnectionName = connectionName, FriendlyName = (discordChannel as ITextChannel)?.Name ?? "UNKNOWN", @@ -226,7 +226,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers lock (this) { mappedChannels.Clear(); - mappedChannels.AddRange(enumerator.Select(x => x.Id)); + mappedChannels.AddRange(enumerator.Select(x => x.RealId)); } return Task.FromResult>(enumerator.ToList()); diff --git a/src/Tgstation.Server.Host/Components/Chat/User.cs b/src/Tgstation.Server.Host/Components/Chat/User.cs index 6b7cbda2ab..c3a5e2b6dc 100644 --- a/src/Tgstation.Server.Host/Components/Chat/User.cs +++ b/src/Tgstation.Server.Host/Components/Chat/User.cs @@ -1,14 +1,28 @@ -namespace Tgstation.Server.Host.Components.Chat +using Newtonsoft.Json; +using System; +using System.Globalization; + +namespace Tgstation.Server.Host.Components.Chat { /// /// Represents a tgs_chat_user datum /// public sealed class User { + /// + /// Backing field for . Represented as a to avoid BYOND percision loss + /// + public string Id { get; set; } + /// /// The internal user id /// - public ulong Id { get; set; } + [JsonIgnore] + public ulong RealId + { + get => UInt64.Parse(Id, CultureInfo.InvariantCulture); + set => Id = value.ToString(CultureInfo.InvariantCulture); + } /// /// The friendly name of the user From 56ca3205bbf9f659e22e0a8fb250cc2cab1176bb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 12:47:28 -0400 Subject: [PATCH 232/397] WIP IrcProvider --- .../Chat/Providers/IrcPasswordType.cs | 9 + .../Components/Chat/Providers/IrcProvider.cs | 175 ++++++++++++++++++ src/Tgstation.Server.Host/Core/Application.cs | 4 + .../Core/IApplication.cs | 5 + 4 files changed, 193 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs new file mode 100644 index 0000000000..38181f9f99 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs @@ -0,0 +1,9 @@ +namespace Tgstation.Server.Host.Components.Chat.Providers +{ + enum IrcPasswordType + { + Server, + Sasl, + NickServ + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs new file mode 100644 index 0000000000..49410fc57d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -0,0 +1,175 @@ +using Meebey.SmartIrc4net; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Chat.Providers +{ + /// + /// for internet relay chat + /// + sealed class IrcProvider : IProvider + { + /// + public bool Connected => throw new NotImplementedException(); + + /// + public string BotMention => throw new NotImplementedException(); + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// The client + /// + readonly IrcFeatures client; + + /// + /// Address of the server to connect to + /// + readonly string address; + /// + /// Port of the server to connect to + /// + readonly ushort port; + /// + /// IRC nickname + /// + readonly string nickname; + /// + /// Password which will used for authentication + /// + readonly string password; + /// + /// The of + /// + readonly IrcPasswordType? passwordType; + + /// + /// Construct an + /// + /// The value of logger + /// The to get the from + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// If should be used + public IrcProvider(ILogger logger, IApplication application, string address, ushort port, string nickname, string password, IrcPasswordType? passwordType, bool useSsl) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (application == null) + throw new ArgumentNullException(nameof(application)); + + this.address = address ?? throw new ArgumentNullException(nameof(address)); + this.port = port; + this.nickname = nickname ?? throw new ArgumentNullException(nameof(nickname)); + + if (passwordType.HasValue && password == null) + throw new ArgumentNullException(nameof(password)); + + if(password != null && !passwordType.HasValue) + throw new ArgumentNullException(nameof(passwordType)); + + this.password = password; + this.passwordType = passwordType; + + client = new IrcFeatures + { + SupportNonRfc = true, + CtcpUserInfo = "You are going to play. And I am going to watch. And everything will be just fine...", + AutoRejoin = true, + AutoRejoinOnKick = true, + AutoRelogin = true, + AutoRetry = true, + AutoRetryLimit = 5, + AutoRetryDelay = 5, + ActiveChannelSyncing = true, + AutoNickHandling = true, + CtcpVersion = application.VersionString, + UseSsl = useSsl, + ValidateServerCertificate = useSsl, + }; + + client.OnChannelMessage += Client_OnChannelMessage; + client.OnQueryMessage += Client_OnQueryMessage; + } + + void Client_OnQueryMessage(object sender, IrcEventArgs e) + { + throw new NotImplementedException(); + } + + void Client_OnChannelMessage(object sender, IrcEventArgs e) + { + throw new NotImplementedException(); + } + + /// + public void Dispose() => Disconnect(default).Wait(); //not actually a task so whatever + + /// + public Task Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + try + { + client.Connect(address, port); + + cancellationToken.ThrowIfCancellationRequested(); + + if (passwordType == IrcPasswordType.Sasl) + { + //TODO + } + + if (passwordType == IrcPasswordType.Server) + client.Login(nickname, nickname, 0, nickname, password); + else + client.Login(nickname, nickname); + + if (passwordType == IrcPasswordType.NickServ) + { + cancellationToken.ThrowIfCancellationRequested(); + client.SendMessage(SendType.Message, "NickServ", String.Format(CultureInfo.InvariantCulture, "IDENTIFY {0}", password)); + } + } + catch(Exception e) + { + logger.LogWarning("Unable to connect to IRC: {0}", e); + } + return true; + }, cancellationToken,TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task Disconnect(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task NextMessage(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 963b22e98a..cd8c07c453 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -30,6 +30,9 @@ namespace Tgstation.Server.Host.Core /// public Version Version { get; } + /// + public string VersionString { get; } + /// /// The for the /// @@ -56,6 +59,7 @@ namespace Tgstation.Server.Host.Core this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); Version = Assembly.GetExecutingAssembly().GetName().Version; + VersionString = String.Format(CultureInfo.InvariantCulture, "/tg/station server v{0}", Version); } /// diff --git a/src/Tgstation.Server.Host/Core/IApplication.cs b/src/Tgstation.Server.Host/Core/IApplication.cs index eed6769ab3..b00ae8a3b9 100644 --- a/src/Tgstation.Server.Host/Core/IApplication.cs +++ b/src/Tgstation.Server.Host/Core/IApplication.cs @@ -7,6 +7,11 @@ namespace Tgstation.Server.Host.Core /// public interface IApplication { + /// + /// A more verbose version of + /// + string VersionString { get; } + /// /// The version of the /// From 8b965483b4373d94cdbd65ddc8cee586e31d2c73 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 13:29:03 -0400 Subject: [PATCH 233/397] More IrcProvider stuff, implement message monitor --- .../Components/Chat/Chat.cs | 79 ++++++++++++++++++- .../Components/Chat/Providers/IrcProvider.cs | 11 +-- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index fb9b656ef0..b1d0b28349 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -45,6 +46,16 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly List trackingContexts; + /// + /// The for + /// + readonly CancellationTokenSource handlerCts; + + /// + /// The that monitors incoming chat messages + /// + Task chatHandler; + /// /// The for the /// @@ -75,12 +86,14 @@ namespace Tgstation.Server.Host.Components.Chat providers = new Dictionary(); mappedChannels = new Dictionary(); trackingContexts = new List(); + handlerCts = new CancellationTokenSource(); channelIdCounter = 1; } /// public void Dispose() { + handlerCts.Dispose(); foreach (var I in providers) I.Value.Dispose(); } @@ -114,6 +127,64 @@ namespace Tgstation.Server.Host.Components.Chat return provider; } + /// + /// Processes a + /// + /// The who recevied + /// The to process + /// The for the operation + /// A representing the running operation + async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken) + { + if (!(message.Content.StartsWith(CommonMention, StringComparison.InvariantCultureIgnoreCase) || message.Content.StartsWith(provider.BotMention, StringComparison.Ordinal))) + //no mention + return; + + await Task.Yield(); + lock (this) + throw new NotImplementedException(); + } + + /// + /// Monitors active providers for new s + /// + /// The for the operation + /// A representing the running operation + async Task MonitorMessages(CancellationToken cancellationToken) + { + var messageTasks = new Dictionary>(); + try + { + while (!cancellationToken.IsCancellationRequested) + { + //prune disconnected providers + foreach (var I in messageTasks) + if (!I.Key.Connected) + messageTasks.Remove(I.Key); + + //add new ones + foreach (var I in providers) + if (I.Value.Connected && !messageTasks.ContainsKey(I.Value)) + messageTasks.Add(I.Value, I.Value.NextMessage(cancellationToken)); + + //wait for a message + var tasks = messageTasks.Select(x => x.Value); + await Task.WhenAny().ConfigureAwait(false); + + //process completed ones + foreach (var I in messageTasks.Where(x => x.Value.IsCompleted)) + { + messageTasks.Remove(I.Key); + + var message = await I.Value.ConfigureAwait(false); + + await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException) { } + } + /// public async Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken) { @@ -221,11 +292,17 @@ namespace Tgstation.Server.Host.Components.Chat public async Task StartAsync(CancellationToken cancellationToken) { await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))).ConfigureAwait(false); + chatHandler = MonitorMessages(handlerCts.Token); started = true; } /// - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Disconnect(cancellationToken))); + public async Task StopAsync(CancellationToken cancellationToken) + { + handlerCts.Cancel(); + await chatHandler.ConfigureAwait(false); + await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Disconnect(cancellationToken))).ConfigureAwait(false); + } /// public async Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 49410fc57d..f1ddcf7a30 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -16,10 +16,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers sealed class IrcProvider : IProvider { /// - public bool Connected => throw new NotImplementedException(); + public bool Connected => client.IsConnected; /// - public string BotMention => throw new NotImplementedException(); + public string BotMention => client.Nickname; /// /// The for the @@ -149,10 +149,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers }, cancellationToken,TaskCreationOptions.LongRunning, TaskScheduler.Current); /// - public Task Disconnect(CancellationToken cancellationToken) + public Task Disconnect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - throw new NotImplementedException(); - } + + + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// public Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) From 238e03ff4190723614f19aeff7d88556e318034c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 15:44:15 -0400 Subject: [PATCH 234/397] Implement SASL among other things --- .../Components/Chat/Chat.cs | 4 +- .../Components/Chat/Message.cs | 2 +- .../Chat/Providers/DiscordProvider.cs | 17 +-- .../Components/Chat/Providers/IProvider.cs | 2 +- .../Chat/Providers/IrcPasswordType.cs | 2 +- .../Components/Chat/Providers/IrcProvider.cs | 129 +++++++++++++++--- 6 files changed, 120 insertions(+), 36 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index b1d0b28349..121ea10a23 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components.Chat readonly Dictionary providers; /// - /// Map of s to s + /// Map of s to s /// readonly Dictionary mappedChannels; @@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Components.Chat ICustomCommandHandler customCommandHandler; /// - /// Used for remapping s + /// Used for remapping s /// ulong channelIdCounter; diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs index 34ba645541..529056d7f3 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Message.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs @@ -1,6 +1,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { - sealed class Message + public sealed class Message { public string Content { get; set; } public User User { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 2f3387b821..437ea7b9ba 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -38,12 +38,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// The for the /// readonly DiscordSocketClient client; - - /// - /// The name used for populating - /// - readonly string connectionName; - + /// /// The token used for connecting to discord /// @@ -68,12 +63,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// Construct a /// /// The value of - /// The value of /// The value of - public DiscordProvider(ILogger logger, string connectionName, string botToken) + public DiscordProvider(ILogger logger, string botToken) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.connectionName = connectionName ?? throw new ArgumentNullException(nameof(connectionName)); this.botToken = botToken ?? throw new ArgumentNullException(nameof(botToken)); client = new DiscordSocketClient(); client.MessageReceived += Client_MessageReceived; @@ -206,7 +199,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (!channel.DiscordChannelId.HasValue) throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); - var discordChannel = client.GetChannel(channel.DiscordChannelId.Value); + var discordChannel = client.GetChannel(channel.DiscordChannelId.Value) as ITextChannel; if (discordChannel == null) return null; @@ -215,8 +208,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { RealId = discordChannel.Id, IsAdmin = channel.IsAdminChannel, - ConnectionName = connectionName, - FriendlyName = (discordChannel as ITextChannel)?.Name ?? "UNKNOWN", + ConnectionName = discordChannel.Guild.Name, + FriendlyName = discordChannel.Name, IsPrivate = false }; }; diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs index 5b6761ad2c..99d13e1768 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -53,7 +53,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// Send a message to the /// - /// The to send to + /// The to send to /// The message contents /// The for the operation /// A representing the running operation diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs index 38181f9f99..e222f5d6ef 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs @@ -1,6 +1,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { - enum IrcPasswordType + public enum IrcPasswordType { Server, Sasl, diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index f1ddcf7a30..c09d55ce43 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -3,6 +3,8 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -15,6 +17,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// sealed class IrcProvider : IProvider { + const int TimeoutSeconds = 5; + /// public bool Connected => client.IsConnected; @@ -52,6 +56,16 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// readonly IrcPasswordType? passwordType; + /// + /// Map of s to channel names + /// + readonly Dictionary channelIdMap; + + /// + /// Id counter for + /// + ulong channelIdCounter; + /// /// Construct an /// @@ -90,31 +104,35 @@ namespace Tgstation.Server.Host.Components.Chat.Providers AutoRejoinOnKick = true, AutoRelogin = true, AutoRetry = true, - AutoRetryLimit = 5, - AutoRetryDelay = 5, + AutoRetryLimit = TimeoutSeconds, + AutoRetryDelay = TimeoutSeconds, ActiveChannelSyncing = true, AutoNickHandling = true, CtcpVersion = application.VersionString, - UseSsl = useSsl, - ValidateServerCertificate = useSsl, + UseSsl = useSsl }; + if (useSsl) + client.ValidateServerCertificate = true; //dunno if it defaults to that or what client.OnChannelMessage += Client_OnChannelMessage; client.OnQueryMessage += Client_OnQueryMessage; + + channelIdMap = new Dictionary(); + channelIdCounter = 0; } void Client_OnQueryMessage(object sender, IrcEventArgs e) { - throw new NotImplementedException(); + } void Client_OnChannelMessage(object sender, IrcEventArgs e) { - throw new NotImplementedException(); + } /// - public void Dispose() => Disconnect(default).Wait(); //not actually a task so whatever + public void Dispose() => client.Disconnect(); //just closes the socket /// public Task Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -125,23 +143,60 @@ namespace Tgstation.Server.Host.Components.Chat.Providers cancellationToken.ThrowIfCancellationRequested(); - if (passwordType == IrcPasswordType.Sasl) - { - //TODO - } - if (passwordType == IrcPasswordType.Server) client.Login(nickname, nickname, 0, nickname, password); else - client.Login(nickname, nickname); + { + if (passwordType == IrcPasswordType.Sasl) + { + 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); + } if (passwordType == IrcPasswordType.NickServ) { cancellationToken.ThrowIfCancellationRequested(); client.SendMessage(SendType.Message, "NickServ", String.Format(CultureInfo.InvariantCulture, "IDENTIFY {0}", password)); } + else if (passwordType == IrcPasswordType.Sasl) + { + //wait for the sasl ack or timeout + var recievedAck = false; + var recievedPlus = false; + client.OnReadLine += (sender, e) => + { + if (e.Line.Contains("ACK :sasl")) + recievedAck = true; + else if (e.Line.Contains("AUTHENTICATE +")) + recievedPlus = true; + }; + + var startTime = DateTimeOffset.Now; + var endTime = DateTimeOffset.Now.AddSeconds(TimeoutSeconds); + cancellationToken.ThrowIfCancellationRequested(); + for(; !recievedAck && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult()) + client.Listen(false); + + client.WriteLine("AUTHENTICATE PLAIN", Priority.Critical); + cancellationToken.ThrowIfCancellationRequested(); + + for (; !recievedPlus && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult()) + client.Listen(false); + + //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); + var chars = authLine.ToCharArray(); + client.WriteLine(authLine, Priority.Critical); + + cancellationToken.ThrowIfCancellationRequested(); + client.WriteLine("CAP END", Priority.Critical); + } } - catch(Exception e) + catch (Exception e) { logger.LogWarning("Unable to connect to IRC: {0}", e); } @@ -151,15 +206,51 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public Task Disconnect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - - + client.RfcQuit(); + Dispose(); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// - public Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) + public Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - throw new NotImplementedException(); - } + if (channels.Any(x => x.IrcChannel == null)) + throw new InvalidOperationException("ChatChannel missing IrcChannel!"); + lock (this) + { + var hs = new HashSet(); //for unique inserts + foreach (var I in channels) + hs.Add(I.IrcChannel); + var toPart = new List(); + foreach (var I in client.JoinedChannels) + if (!hs.Remove(I)) + toPart.Add(I); + + foreach (var I in toPart) + client.RfcPart(I); + foreach (var I in hs) + client.RfcJoin(I); + + return (IReadOnlyList)channels.Select(x => { + var id = ++channelIdCounter; + if (!channelIdMap.Any(y => + { + if (y.Value != x.IrcChannel) + return false; + id = y.Key; + return true; + })) + channelIdMap.Add(id, x.IrcChannel); + return new Channel + { + RealId = id, + IsAdmin = x.IsAdminChannel, + ConnectionName = address, + FriendlyName = channelIdMap[id], + IsPrivate = false + }; + }).ToList(); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// public Task NextMessage(CancellationToken cancellationToken) From 9c9ba0ad293be7805aebfef1d9194c0bb6887fc6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 16:32:50 -0400 Subject: [PATCH 235/397] Finish implementing IrcProvider --- .../Chat/Providers/DiscordProvider.cs | 60 ++------ .../Components/Chat/Providers/IrcProvider.cs | 137 ++++++++++++++---- .../Components/Chat/Providers/Provider.cs | 76 ++++++++++ 3 files changed, 195 insertions(+), 78 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 437ea7b9ba..4412cf16b5 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -13,13 +13,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// for the Discord app /// - sealed class DiscordProvider : IProvider + sealed class DiscordProvider : Provider { /// - public bool Connected { get; private set; } + public override bool Connected => client.ConnectionState == ConnectionState.Connected; /// - public string BotMention + public override string BotMention { get { @@ -43,22 +43,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// The token used for connecting to discord /// readonly string botToken; - - /// - /// of received s - /// - readonly Queue messageQueue; /// /// of mapped s /// readonly List mappedChannels; - /// - /// that completes while isn't empty - /// - TaskCompletionSource nextMessage; - /// /// Construct a /// @@ -70,13 +60,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers this.botToken = botToken ?? throw new ArgumentNullException(nameof(botToken)); client = new DiscordSocketClient(); client.MessageReceived += Client_MessageReceived; - nextMessage = new TaskCompletionSource(); mappedChannels = new List(); - messageQueue = new Queue(); } /// - public void Dispose() => client.Dispose(); + public override void Dispose() => client.Dispose(); /// /// Handle a message recieved from Discord @@ -103,39 +91,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers RealId = e.Channel.Id, IsAdmin = false, IsPrivate = true, - ConnectionName = connectionName, + ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", FriendlyName = e.Channel.Name }, FriendlyName = e.Author.Username, Mention = e.Author.Mention } }; - - lock (this) - { - messageQueue.Enqueue(result); - nextMessage.TrySetResult(null); - } + EnqueueMessage(result); return Task.CompletedTask; } /// - public async Task NextMessage(CancellationToken cancellationToken) - { - var cancelTcs = new TaskCompletionSource(); - using (cancellationToken.Register(() => cancelTcs.SetCanceled())) - await Task.WhenAny(nextMessage.Task, cancelTcs.Task).ConfigureAwait(false); - lock (this) - { - var result = messageQueue.Dequeue(); - if (messageQueue.Count == 0) - nextMessage = new TaskCompletionSource(); - return result; - } - } - - /// - public async Task Connect(CancellationToken cancellationToken) + public override async Task Connect(CancellationToken cancellationToken) { if (Connected) return true; @@ -162,12 +130,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers logger.LogWarning("Error connecting to Discord: {0}", e); return false; } - - Connected = true; + return true; } - public async Task Disconnect(CancellationToken cancellationToken) + public override async Task Disconnect(CancellationToken cancellationToken) { if (!Connected) return; @@ -182,11 +149,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { logger.LogWarning("Error disconnecting from discord: {0}", e); } - Connected = false; } /// - public Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) + public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) { if (channels == null) throw new ArgumentNullException(nameof(channels)); @@ -199,9 +165,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (!channel.DiscordChannelId.HasValue) throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); - var discordChannel = client.GetChannel(channel.DiscordChannelId.Value) as ITextChannel; - - if (discordChannel == null) + if (!(client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel)) return null; return new Channel @@ -226,7 +190,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) { + public override async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) { try { await ((client.GetChannel(channelId) as ITextChannel)?.SendMessageAsync(message, false, null, new RequestOptions diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index c09d55ce43..7457d6f8d2 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -15,15 +15,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// for internet relay chat /// - sealed class IrcProvider : IProvider + sealed class IrcProvider : Provider { const int TimeoutSeconds = 5; /// - public bool Connected => client.IsConnected; + public override bool Connected => client.IsConnected; /// - public string BotMention => client.Nickname; + public override string BotMention => client.Nickname; /// /// The for the @@ -61,6 +61,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// readonly Dictionary channelIdMap; + /// + /// Map of s to query users + /// + readonly Dictionary queryChannelIdMap; + /// /// Id counter for /// @@ -118,24 +123,82 @@ namespace Tgstation.Server.Host.Components.Chat.Providers client.OnQueryMessage += Client_OnQueryMessage; channelIdMap = new Dictionary(); - channelIdCounter = 0; - } - - void Client_OnQueryMessage(object sender, IrcEventArgs e) - { - - } - - void Client_OnChannelMessage(object sender, IrcEventArgs e) - { - + queryChannelIdMap = new Dictionary(); + channelIdCounter = 1; } /// - public void Dispose() => client.Disconnect(); //just closes the socket + public override void Dispose() => client.Disconnect(); //just closes the socket + + /// + /// Handle an IRC message + /// + /// The + /// If this is a query message + void HandleMessage(IrcEventArgs e, bool isPrivate) + { + if (e.Data.From.ToUpperInvariant() == client.Nickname.ToUpperInvariant()) + return; + + var username = e.Data.From; + var channelName = isPrivate ? username : e.Data.Channel; + ulong channelId = 0; + lock (this) + { + var dicToCheck = isPrivate ? queryChannelIdMap : channelIdMap; + if (!dicToCheck.Any(x => + { + if (x.Value != channelName) + return false; + channelId = x.Key; + return true; + })) + { + channelId = ++channelIdCounter; + dicToCheck.Add(channelId, channelName); + if (isPrivate) + channelIdMap.Add(channelId, null); + } + } + + var message = new Message + { + Content = e.Data.Message, + User = new User + { + Channel = new Channel + { + IsAdmin = false, + ConnectionName = address, + FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName, + RealId = channelId, + IsPrivate = isPrivate + }, + FriendlyName = username, + RealId = channelId, + Mention = username + } + }; + + EnqueueMessage(message); + } + + /// + /// When a query message is received in IRC + /// + /// The sender of the event + /// The + void Client_OnQueryMessage(object sender, IrcEventArgs e) => HandleMessage(e, true); + + /// + /// When a channel message is received in IRC + /// + /// The sender of the event + /// The + void Client_OnChannelMessage(object sender, IrcEventArgs e) => HandleMessage(e, false); /// - public Task Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public override Task Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => { try { @@ -204,14 +267,21 @@ namespace Tgstation.Server.Host.Components.Chat.Providers }, cancellationToken,TaskCreationOptions.LongRunning, TaskScheduler.Current); /// - public Task Disconnect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public override Task Disconnect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - client.RfcQuit(); + try + { + client.RfcQuit(); + } + catch (Exception e) + { + logger.LogWarning("Error quitting IRC: {0}", e); + } Dispose(); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// - public Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { if (channels.Any(x => x.IrcChannel == null)) throw new InvalidOperationException("ChatChannel missing IrcChannel!"); @@ -230,8 +300,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers foreach (var I in hs) client.RfcJoin(I); + + return (IReadOnlyList)channels.Select(x => { - var id = ++channelIdCounter; + ulong id = channelIdCounter; if (!channelIdMap.Any(y => { if (y.Value != x.IrcChannel) @@ -240,6 +312,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return true; })) channelIdMap.Add(id, x.IrcChannel); + else + ++channelIdCounter; return new Channel { RealId = id, @@ -251,17 +325,20 @@ namespace Tgstation.Server.Host.Components.Chat.Providers }).ToList(); } }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); - + /// - public Task NextMessage(CancellationToken cancellationToken) + public override Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - throw new NotImplementedException(); - } - - /// - public Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + var channelName = channelIdMap[channelId] ?? queryChannelIdMap[channelId]; + try + { + if (client.JoinedChannels.Contains(channelName)) + client.SendMessage(SendType.Message, channelName, message); + } + catch(Exception e) + { + logger.LogWarning("Unable to send to channel: {0}", e); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs new file mode 100644 index 0000000000..01484c229d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Components.Chat.Providers +{ + /// + abstract class Provider : IProvider + { + /// + /// of received s + /// + readonly Queue messageQueue; + + /// + /// that completes while isn't empty + /// + TaskCompletionSource nextMessage; + + protected Provider() + { + messageQueue = new Queue(); + nextMessage = new TaskCompletionSource(); + } + + /// + public abstract bool Connected { get; } + + /// + public abstract string BotMention { get; } + + /// + /// Queues a for + /// + /// The to queue + protected void EnqueueMessage(Message message) + { + lock (messageQueue) + { + messageQueue.Enqueue(message); + nextMessage.TrySetResult(null); + } + } + + /// + public abstract void Dispose(); + + /// + public abstract Task Connect(CancellationToken cancellationToken); + + /// + public abstract Task Disconnect(CancellationToken cancellationToken); + + /// + public abstract Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken); + + /// + public async Task NextMessage(CancellationToken cancellationToken) + { + var cancelTcs = new TaskCompletionSource(); + using (cancellationToken.Register(() => cancelTcs.SetCanceled())) + await Task.WhenAny(nextMessage.Task, cancelTcs.Task).ConfigureAwait(false); + lock (messageQueue) + { + var result = messageQueue.Dequeue(); + if (messageQueue.Count == 0) + nextMessage = new TaskCompletionSource(); + return result; + } + } + + /// + public abstract Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken); + } +} From 1d931f1954914544586d252c2d5d013ebf213b48 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 16:37:02 -0400 Subject: [PATCH 236/397] More docs and stuff --- .../Components/Chat/ChannelMapping.cs | 17 +++++++++++++++++ .../Components/Chat/Message.cs | 12 +++++++++++- .../Chat/Providers/IrcPasswordType.cs | 14 +++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs index a1ed4dd808..5185f033cc 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs @@ -1,11 +1,28 @@ namespace Tgstation.Server.Host.Components.Chat { + /// + /// Represents a mapping of a + /// sealed class ChannelMapping { + /// + /// The Id of the + /// public long ProviderId { get; set; } + + /// + /// The original + /// public ulong ProviderChannelId { get; set; } + + /// + /// If is a watchdog channel + /// public bool IsWatchdogChannel { get; set; } + /// + /// The with the mapped Id + /// public Channel Channel { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs index 529056d7f3..3989369ce8 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Message.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs @@ -1,8 +1,18 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { - public sealed class Message + /// + /// Represents a message recieved by a + /// + sealed class Message { + /// + /// The text of the message + /// public string Content { get; set; } + + /// + /// The who sent the + /// public User User { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs index e222f5d6ef..d31a3aba73 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcPasswordType.cs @@ -1,9 +1,21 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { - public enum IrcPasswordType + /// + /// Represents the type of a password passed to the constructor of + /// + enum IrcPasswordType { + /// + /// Use server authentication + /// Server, + /// + /// Use PLAIN sasl authentication + /// Sasl, + /// + /// Use NickServ authentication + /// NickServ } } From 5ae57ea71dad9a3e6d5131b3e769f9d556ce51cf Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 16:54:58 -0400 Subject: [PATCH 237/397] Finish up more chat stuff --- .../Components/Chat/Chat.cs | 52 ++++++++++++++++--- .../Components/Chat/ICommandFactory.cs | 7 +++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 121ea10a23..77fb9031b2 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -1,6 +1,7 @@ -using System; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -26,6 +27,11 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly ILogger logger; + /// /// s that never change /// @@ -69,18 +75,20 @@ namespace Tgstation.Server.Host.Components.Chat /// /// If has been called /// - bool started; + bool started; /// /// Construct a /// /// The value of /// The value of + /// The value of /// The used to populate - public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory) + public Chat(IProviderFactory providerFactory, IIOManager ioManager, ILogger logger, ICommandFactory commandFactory) { this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); builtinCommands = commandFactory?.GenerateCommands() ?? throw new ArgumentNullException(nameof(commandFactory)); providers = new Dictionary(); @@ -136,13 +144,41 @@ namespace Tgstation.Server.Host.Components.Chat /// A representing the running operation async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken) { + logger.LogTrace("Chat message: {0}. User (Note unconverted provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User)); + if (customCommandHandler == null) + { + logger.LogError("Recieved chat message with no command handler installed!"); + return; + } + if (!(message.Content.StartsWith(CommonMention, StringComparison.InvariantCultureIgnoreCase) || message.Content.StartsWith(provider.BotMention, StringComparison.Ordinal))) //no mention return; - - await Task.Yield(); - lock (this) - throw new NotImplementedException(); + + var splits = new List(message.Content.Split(' ')); + if (splits.Count == 1) + { + //just a mention + await SendMessage("Hi!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); + return; + } + + splits.RemoveAt(0); + + var command = splits[0]; + splits.RemoveAt(0); + var arguments = String.Join(" ", splits); + + if (customCommandHandler == null) + logger.LogError("Recieved chat message with no command handler installed!"); + try + { + await customCommandHandler.HandleChatCommand(command, arguments, message.User, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning("Error processing custom command: {0}", e); + } } /// diff --git a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs index 8abfe7abcd..4fb9dd2de1 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs @@ -3,8 +3,15 @@ using Tgstation.Server.Host.Components.Chat.Commands; namespace Tgstation.Server.Host.Components.Chat { + /// + /// Factory for built in s + /// interface ICommandFactory { + /// + /// Generate builtin s + /// + /// A of s IReadOnlyList GenerateCommands(); } } From e7562cc6193801483cc2aa25c9f8374b277d94a5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 18 Jul 2018 17:05:52 -0400 Subject: [PATCH 238/397] Implement VersionCommand --- .../Components/Chat/Chat.cs | 12 ++++--- .../Components/Chat/Commands/Command.cs | 36 ++++++++----------- .../Components/Chat/Commands/ICommand.cs | 34 ++++++++++++++++++ .../Chat/Commands/VersionCommand.cs | 30 ++++++++++++++++ .../Components/Chat/ICommandFactory.cs | 8 ++--- .../Components/Chat/ICustomCommandHandler.cs | 5 ++- 6 files changed, 92 insertions(+), 33 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 77fb9031b2..9ff74209ef 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -33,9 +33,9 @@ namespace Tgstation.Server.Host.Components.Chat readonly ILogger logger; /// - /// s that never change + /// s that never change /// - readonly IReadOnlyList builtinCommands; + readonly IReadOnlyList builtinCommands; /// /// Map of s in use, keyed by @@ -173,11 +173,15 @@ namespace Tgstation.Server.Host.Components.Chat logger.LogError("Recieved chat message with no command handler installed!"); try { - await customCommandHandler.HandleChatCommand(command, arguments, message.User, cancellationToken).ConfigureAwait(false); + var result = await customCommandHandler.HandleChatCommand(command, arguments, message.User, cancellationToken).ConfigureAwait(false); + if(result != null) + await SendMessage(result, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); } catch (Exception e) { - logger.LogWarning("Error processing custom command: {0}", e); + //error bc custom commands should reply about why it failed + logger.LogError("Error processing custom command: {0}", e); + await SendMessage("Internal error processing command!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs index d5f192ec59..b6ef36467e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs @@ -1,29 +1,21 @@ -namespace Tgstation.Server.Host.Components.Chat.Commands +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.Chat.Commands { - /// - /// Represents a command that can be invoked by talking to chat bots - /// - public abstract class Command + /// + public abstract class Command : ICommand { - /// - /// The text to invoke the command. May not be "?" or "help" (case-insensitive) - /// - public string Name { get; set; } + /// + public string Name { get; protected set; } - /// - /// The help text to display when queires are made about the command - /// - public string HelpText { get; set; } + /// + public string HelpText { get; protected set; } - /// - /// If the command should only be available to s who's has set - /// - public bool AdminOnly { get; set; } + /// + public bool AdminOnly { get; protected set; } - /// - /// Invoke the - /// - /// The text after with leading whitespace trimmed - public abstract void Invoke(string arguments); + /// + public abstract Task Invoke(string arguments, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs new file mode 100644 index 0000000000..0b5f58ffde --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs @@ -0,0 +1,34 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.Chat.Commands +{ + /// + /// Represents a command that can be invoked by talking to chat bots + /// + public interface ICommand + { + /// + /// The text to invoke the command. May not be "?" or "help" (case-insensitive) + /// + string Name { get; } + + /// + /// The help text to display when queires are made about the command + /// + string HelpText { get; } + + /// + /// If the command should only be available to s who's has set + /// + bool AdminOnly { get; } + + /// + /// Invoke the + /// + /// The text after with leading whitespace trimmed + /// The for the operation + /// A resulting in a to send to the invoker + Task Invoke(string arguments, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs new file mode 100644 index 0000000000..57fe1b865c --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Chat.Commands +{ + /// + /// to return the + /// + sealed class VersionCommand : Command + { + /// + /// The for the + /// + readonly IApplication application; + + /// + /// Construct a + /// + /// + public VersionCommand(IApplication application) + { + this.application = application ?? throw new ArgumentNullException(nameof(application)); + } + + /// + public override Task Invoke(string arguments, CancellationToken cancellationToken) => Task.FromResult(application.VersionString); + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs index 4fb9dd2de1..15827975ac 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs @@ -4,14 +4,14 @@ using Tgstation.Server.Host.Components.Chat.Commands; namespace Tgstation.Server.Host.Components.Chat { /// - /// Factory for built in s + /// Factory for built in s /// interface ICommandFactory { /// - /// Generate builtin s + /// Generate builtin s /// - /// A of s - IReadOnlyList GenerateCommands(); + /// A of s + IReadOnlyList GenerateCommands(); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs b/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs index 5106ba0efd..9f640cb633 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ICustomCommandHandler.cs @@ -1,11 +1,10 @@ -using System.Collections.Generic; -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Chat { /// - /// Handles that map to those defined in a + /// Handles s that map to those defined in a /// public interface ICustomCommandHandler { From 37dab01632195cb81d3a0c067296c17069283a99 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 18 Jul 2018 23:13:53 -0400 Subject: [PATCH 239/397] Fix private channel detection. Cleanup everywhere --- src/DMAPI/tgs/v4/api.dm | 2 +- .../Components/Chat/Chat.cs | 46 +++++++----- .../Components/Chat/ChatFactory.cs | 41 +++++++++++ .../Components/Chat/Commands/Command.cs | 2 +- .../Chat/Commands/CommandFactory.cs | 30 ++++++++ .../Components/Chat/Commands/CustomCommand.cs | 5 +- .../Components/Chat/Commands/ICommand.cs | 3 +- .../Chat/Commands/VersionCommand.cs | 2 +- .../Components/Chat/IChatFactory.cs | 14 ++++ .../Components/EventType.cs | 2 +- .../Components/IEventConsumer.cs | 2 +- .../Components/IInstanceFactory.cs | 9 ++- .../Components/InstanceFactory.cs | 72 +++++++++++++++++-- .../Components/Watchdog/IWatchdog.cs | 2 +- .../Watchdog/SessionControllerFactory.cs | 14 ++-- .../Components/Watchdog/Watchdog.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 16 ++++- 17 files changed, 220 insertions(+), 44 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index a6700ab4e5..e62de1c25e 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -211,7 +211,7 @@ channel.friendly_name = channel_json["friendly_name"] channel.connection_name = channel_json["connection_name"] channel.is_admin_channel = channel_json["is_admin_channel"] - channel.is_admin_channel = channel_json["is_private_channel"] || FALSE + channel.is_private_channel = channel_json["is_private_channel"] || FALSE return channel #undef TGS4_TOPIC_COMMAND diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 9ff74209ef..04f5c5ac68 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -33,9 +33,9 @@ namespace Tgstation.Server.Host.Components.Chat readonly ILogger logger; /// - /// s that never change + /// Unchanging s in the mapped by /// - readonly IReadOnlyList builtinCommands; + readonly Dictionary builtinCommands; /// /// Map of s in use, keyed by @@ -57,16 +57,16 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly CancellationTokenSource handlerCts; + /// + /// The for the + /// + ICustomCommandHandler customCommandHandler; + /// /// The that monitors incoming chat messages /// Task chatHandler; - /// - /// The for the - /// - ICustomCommandHandler customCommandHandler; - /// /// Used for remapping s /// @@ -89,7 +89,9 @@ namespace Tgstation.Server.Host.Components.Chat this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - builtinCommands = commandFactory?.GenerateCommands() ?? throw new ArgumentNullException(nameof(commandFactory)); + builtinCommands = new Dictionary(); + foreach (var I in commandFactory?.GenerateCommands() ?? throw new ArgumentNullException(nameof(commandFactory))) + builtinCommands.Add(I.Name, I); providers = new Dictionary(); mappedChannels = new Dictionary(); @@ -145,11 +147,6 @@ namespace Tgstation.Server.Host.Components.Chat async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken) { logger.LogTrace("Chat message: {0}. User (Note unconverted provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User)); - if (customCommandHandler == null) - { - logger.LogError("Recieved chat message with no command handler installed!"); - return; - } if (!(message.Content.StartsWith(CommonMention, StringComparison.InvariantCultureIgnoreCase) || message.Content.StartsWith(provider.BotMention, StringComparison.Ordinal))) //no mention @@ -165,22 +162,33 @@ namespace Tgstation.Server.Host.Components.Chat splits.RemoveAt(0); - var command = splits[0]; + var command = splits[0].ToUpperInvariant(); splits.RemoveAt(0); var arguments = String.Join(" ", splits); - - if (customCommandHandler == null) - logger.LogError("Recieved chat message with no command handler installed!"); + try { - var result = await customCommandHandler.HandleChatCommand(command, arguments, message.User, cancellationToken).ConfigureAwait(false); + if (!builtinCommands.TryGetValue(command, out ICommand commandHandler)) + { + var tasks = trackingContexts.Select(x => x.GetCustomCommands(cancellationToken)); + await Task.WhenAll(tasks).ConfigureAwait(false); + commandHandler = tasks.SelectMany(x => x.Result).Where(x => x.Name.ToUpperInvariant() == command).FirstOrDefault(); + } + + if (command == default) + { + await SendMessage("Invalid command! Type '?' or 'help' for available commands.", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); + return; + } + + var result = await commandHandler.Invoke(arguments, message.User, cancellationToken).ConfigureAwait(false); if(result != null) await SendMessage(result, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); } catch (Exception e) { //error bc custom commands should reply about why it failed - logger.LogError("Error processing custom command: {0}", e); + logger.LogError("Error processing chat command: {0}", e); await SendMessage("Internal error processing command!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs new file mode 100644 index 0000000000..5a7b05ae0e --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Logging; +using System; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Chat +{ + /// + sealed class ChatFactory : IChatFactory + { + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// The for the + /// + readonly ILoggerFactory loggerFactory; + + /// + /// The for the + /// + readonly ICommandFactory commandFactory; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + public ChatFactory(IIOManager ioManager, ILoggerFactory loggerFactory, ICommandFactory commandFactory) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); + } + + /// + public IChat CreateChat() => new Chat(new ProviderFactory(), ioManager, loggerFactory.CreateLogger(), commandFactory); + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs index b6ef36467e..94d7456133 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs @@ -16,6 +16,6 @@ namespace Tgstation.Server.Host.Components.Chat.Commands public bool AdminOnly { get; protected set; } /// - public abstract Task Invoke(string arguments, CancellationToken cancellationToken); + public abstract Task Invoke(string arguments, User user, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs new file mode 100644 index 0000000000..fd1e731c5e --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Chat.Commands +{ + /// + sealed class CommandFactory : ICommandFactory + { + /// + /// The for the + /// + readonly IApplication application; + + /// + /// Construct a + /// + /// The value of + public CommandFactory(IApplication application) + { + this.application = application ?? throw new ArgumentNullException(nameof(application)); + } + + /// + public IReadOnlyList GenerateCommands() => new List + { + new VersionCommand(application) + }; + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs index 6221f531c1..64ba61c72d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Chat.Commands { @@ -24,10 +26,11 @@ namespace Tgstation.Server.Host.Components.Chat.Commands } /// - public override void Invoke(string arguments) + public override Task Invoke(string arguments, User user, CancellationToken cancellationToken) { if (handler == null) throw new InvalidOperationException("SetHandler() has not been called!"); + return handler.HandleChatCommand(Name, arguments, user, cancellationToken); } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs index 0b5f58ffde..8694606731 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommand.cs @@ -27,8 +27,9 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// Invoke the /// /// The text after with leading whitespace trimmed + /// The who invoked the command /// The for the operation /// A resulting in a to send to the invoker - Task Invoke(string arguments, CancellationToken cancellationToken); + Task Invoke(string arguments, User user, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs index 57fe1b865c..62e7f8228c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs @@ -25,6 +25,6 @@ namespace Tgstation.Server.Host.Components.Chat.Commands } /// - public override Task Invoke(string arguments, CancellationToken cancellationToken) => Task.FromResult(application.VersionString); + public override Task Invoke(string arguments, User user, CancellationToken cancellationToken) => Task.FromResult(application.VersionString); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs new file mode 100644 index 0000000000..734b6243f3 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs @@ -0,0 +1,14 @@ +namespace Tgstation.Server.Host.Components.Chat +{ + /// + /// For creating s + /// + interface IChatFactory + { + /// + /// Create a + /// + /// A new + IChat CreateChat(); + } +} diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs index 53e6c9c9bd..615c7ed3bb 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -3,7 +3,7 @@ /// /// Types of events /// - enum EventType + public enum EventType { /// /// Parameters: Reference name, commit sha diff --git a/src/Tgstation.Server.Host/Components/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/IEventConsumer.cs index 322860c7b3..986f9f2213 100644 --- a/src/Tgstation.Server.Host/Components/IEventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/IEventConsumer.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Components /// /// Consumes s and takes the appropriate actions /// - interface IEventConsumer + public interface IEventConsumer { /// /// Handle a given diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index d9d4f4cda8..600a71ecc0 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Components.Watchdog; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { @@ -10,8 +11,10 @@ namespace Tgstation.Server.Host.Components /// /// Create an /// - /// The + /// The + /// The for the + /// The for the /// A new - IInstance CreateInstance(Host.Models.Instance metadata); + IInstance CreateInstance(Models.Instance metadata, IInteropRegistrar interopRegistrar, IReattachInfoHandler reattachInfoHandler); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 7939caed1b..e31d403ace 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -1,5 +1,11 @@ -using System; +using Byond.TopicSender; +using Microsoft.Extensions.Logging; +using System; +using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Chat.Commands; +using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components { @@ -16,22 +22,67 @@ namespace Tgstation.Server.Host.Components /// readonly IDatabaseContextFactory databaseContextFactory; + /// + /// The for the + /// + readonly IApplication application; + + /// + /// The for the + /// + readonly ILoggerFactory loggerFactory; + + /// + /// The for the + /// + readonly IByondTopicSender byondTopicSender; + + /// + /// The for the + /// + readonly IServerUpdater serverUpdater; + + /// + /// The for the + /// + readonly ICryptographySuite cryptographySuite; + + /// + /// The for the + /// + readonly IExecutor executor; + + /// + /// The for the + /// + readonly ICommandFactory commandFactory; + /// /// Construct an /// /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory) + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite )); + this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); } /// - public IInstance CreateInstance(Models.Instance metadata) + public IInstance CreateInstance(Models.Instance metadata, IInteropRegistrar interopRegistrar, IReattachInfoHandler reattachInfoHandler) { //Create the ioManager for the instance - var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path); //various other ioManagers @@ -42,10 +93,23 @@ namespace Tgstation.Server.Host.Components var codeModificationsIoMananger = new ResolvingIOManager(instanceIoManager, "CodeModifications"); var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, metadata); + var commandFactory = new CommandFactory(application); + var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory); + var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); + IByond byond = null; + IConfiguration configuration = null; + + var chat = chatFactory.CreateChat(); + var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata); + var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, metadata); + var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings); + + var dreamMaker = new DreamMaker(byond, ioManager, configuration, sessionControllerFactory, dmbFactory, application, watchdog, loggerFactory.CreateLogger()); throw new NotImplementedException(); + //return new Instance(metadata, repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory); } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 99e19e7ef0..c7533c58ca 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Runs and monitors the twin server controllers /// - public interface IWatchdog : IHostedService, IDisposable + public interface IWatchdog : IHostedService, IDisposable, IEventConsumer { /// /// If the watchdog is running diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 4f2dc78885..696d51016c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -47,11 +47,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IApplication application; - /// - /// The for the - /// - readonly IInstance instance; - /// /// The for the /// @@ -67,6 +62,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly ILoggerFactory loggerFactory; + /// + /// The for the + /// + readonly Models.Instance instance; + /// /// Construct a /// @@ -80,7 +80,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public SessionControllerFactory(IExecutor executor, IByond byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IInstance instance, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory) + public SessionControllerFactory(IExecutor executor, IByond byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Models.Instance instance) { this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); @@ -111,7 +111,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ChatChannelsJson = GuidJsonFile(), ChatCommandsJson = GuidJsonFile(), HostPath = application.HostingPath, - InstanceName = instance.GetMetadata().Name, + InstanceName = instance.Name, Revision = dmbProvider.CompileJob.RevisionInformation }; interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.TestMerges.Select(x => new TestMerge diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index c64d6212c7..6129f45488 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -15,7 +15,7 @@ using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog { /// - sealed class Watchdog : IWatchdog, IEventConsumer, ICustomCommandHandler + sealed class Watchdog : IWatchdog, ICustomCommandHandler { /// /// The time in milliseconds to wait from starting to start . Does not take responsiveness into account diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index cd8c07c453..3395876d70 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Byond.TopicSender; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; @@ -14,6 +15,9 @@ using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Reflection; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Chat.Commands; +using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Controllers; using Tgstation.Server.Host.Models; @@ -141,10 +145,18 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); - services.AddSingleton, PasswordHasher>(); + services.AddSingleton, PasswordHasher>(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(new ByondTopicSender + { + ReceiveTimeout = 5000, + SendTimeout = 5000 + }); + services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(); From d76b2d561fb5cdd21b5a74c2f29807e51958a429 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 19 Jul 2018 01:29:42 -0400 Subject: [PATCH 240/397] Stufff --- .../Components/IInstance.cs | 2 +- .../Components/IInstanceFactory.cs | 3 +- .../Components/Instance.cs | 41 --------- .../Components/InstanceFactory.cs | 10 ++- .../Components/InstanceManager.cs | 2 +- .../Components/ReattachInfoHandler.cs | 83 +++++++++++++++++++ src/Tgstation.Server.Host/Core/Application.cs | 2 +- 7 files changed, 94 insertions(+), 49 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 54cd4468d6..9868430342 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components /// /// For interacting with the instance services /// - public interface IInstance : IHostedService, IReattachInfoHandler + public interface IInstance : IHostedService { /// /// The for the diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index 600a71ecc0..ea9ed59f44 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -13,8 +13,7 @@ namespace Tgstation.Server.Host.Components /// /// The /// The for the - /// The for the /// A new - IInstance CreateInstance(Models.Instance metadata, IInteropRegistrar interopRegistrar, IReattachInfoHandler reattachInfoHandler); + IInstance CreateInstance(Models.Instance metadata, IInteropRegistrar interopRegistrar); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index b97f722e0c..d607053ab2 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -157,46 +157,5 @@ namespace Tgstation.Server.Host.Components timerTask = TimerLoop(newInterval.Value, timerCts.Token); } } - - /// - public Task Save(WatchdogReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => - { - var instance = new Models.Instance { Id = metadata.Id }; - db.Instances.Attach(instance); - - Models.ReattachInformation ConvertReattachInfo(ReattachInformation wdInfo) - { - db.CompileJobs.Attach(wdInfo.Dmb.CompileJob); - return new Models.ReattachInformation - { - AccessIdentifier = wdInfo.AccessIdentifier, - ChatChannelsJson = wdInfo.ChatChannelsJson, - ChatCommandsJson = wdInfo.ChatCommandsJson, - CompileJob = wdInfo.Dmb.CompileJob, - IsPrimary = wdInfo.IsPrimary, - Port = wdInfo.Port, - ProcessId = wdInfo.ProcessId, - RebootState = wdInfo.RebootState - }; - } - - instance.WatchdogReattachInformation = new Models.WatchdogReattachInformation - { - Alpha = ConvertReattachInfo(reattachInformation.Alpha), - Bravo = ConvertReattachInfo(reattachInformation.Bravo), - AlphaIsActive = reattachInformation.AlphaIsActive, - }; - await db.Save(cancellationToken).ConfigureAwait(false); - }); - - /// - public async Task Load(CancellationToken cancellationToken) - { - Models.WatchdogReattachInformation result = null; - await databaseContextFactory.UseContext(async (db) => - result = await db.Instances.Where(x => x.Id == metadata.Id).Select(x => x.WatchdogReattachInformation).FirstAsync(cancellationToken).ConfigureAwait(false) - ).ConfigureAwait(false); - return new WatchdogReattachInformation(result, dmbFactory); - } } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index e31d403ace..efbefbe737 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -56,7 +56,7 @@ namespace Tgstation.Server.Host.Components /// The for the /// readonly ICommandFactory commandFactory; - + /// /// Construct an /// @@ -64,23 +64,26 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The value of /// The value of /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory) + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite )); this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); + this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); } /// - public IInstance CreateInstance(Models.Instance metadata, IInteropRegistrar interopRegistrar, IReattachInfoHandler reattachInfoHandler) + public IInstance CreateInstance(Models.Instance metadata, IInteropRegistrar interopRegistrar) { //Create the ioManager for the instance var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path); @@ -103,6 +106,7 @@ namespace Tgstation.Server.Host.Components var chat = chatFactory.CreateChat(); var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata); + var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata); var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, metadata); var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings); diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index ff78b157b8..24dd107c18 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Components { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); - var instance = instanceFactory.CreateInstance(metadata); + var instance = instanceFactory.CreateInstance(metadata, this); lock (this) { if (instances.ContainsKey(metadata.Id)) diff --git a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs new file mode 100644 index 0000000000..93c7e6aa91 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Watchdog; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class ReattachInfoHandler: IReattachInfoHandler + { + /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + + /// + /// The for the + /// + readonly IDmbFactory dmbFactory; + + /// + /// The for the + /// + readonly Models.Instance metadata; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + public ReattachInfoHandler(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, Models.Instance metadata) + { + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); + this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); + } + + /// + public Task Save(WatchdogReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => + { + var instance = new Models.Instance { Id = metadata.Id }; + db.Instances.Attach(instance); + + Models.ReattachInformation ConvertReattachInfo(ReattachInformation wdInfo) + { + db.CompileJobs.Attach(wdInfo.Dmb.CompileJob); + return new Models.ReattachInformation + { + AccessIdentifier = wdInfo.AccessIdentifier, + ChatChannelsJson = wdInfo.ChatChannelsJson, + ChatCommandsJson = wdInfo.ChatCommandsJson, + CompileJob = wdInfo.Dmb.CompileJob, + IsPrimary = wdInfo.IsPrimary, + Port = wdInfo.Port, + ProcessId = wdInfo.ProcessId, + RebootState = wdInfo.RebootState + }; + } + + instance.WatchdogReattachInformation = new Models.WatchdogReattachInformation + { + Alpha = ConvertReattachInfo(reattachInformation.Alpha), + Bravo = ConvertReattachInfo(reattachInformation.Bravo), + AlphaIsActive = reattachInformation.AlphaIsActive, + }; + await db.Save(cancellationToken).ConfigureAwait(false); + }); + + /// + public async Task Load(CancellationToken cancellationToken) + { + Models.WatchdogReattachInformation result = null; + await databaseContextFactory.UseContext(async (db) => + result = await db.Instances.Where(x => x.Id == metadata.Id).Select(x => x.WatchdogReattachInformation).FirstAsync(cancellationToken).ConfigureAwait(false) + ).ConfigureAwait(false); + return new WatchdogReattachInformation(result, dmbFactory); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 3395876d70..ae4489d387 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -148,7 +148,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton, PasswordHasher>(); services.AddSingleton(); services.AddSingleton(); - + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(new ByondTopicSender From 71ebb63398d179608b407126961f57100c45ac61 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 12:01:49 -0400 Subject: [PATCH 241/397] Fix some db stuff. Removes failing test --- src/Tgstation.Server.Api/Models/CompileJob.cs | 5 +++++ .../Models/Internal/CompileJob.cs | 5 ----- src/Tgstation.Server.Host/Components/DreamMaker.cs | 2 +- .../Components/Watchdog/SessionControllerFactory.cs | 4 ++-- src/Tgstation.Server.Host/Models/CompileJob.cs | 11 +++++++++-- src/Tgstation.Server.Host/Models/DatabaseContext.cs | 1 + .../Core/TestApplication.cs | 5 ++--- 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs index 0d381b559b..44a3b7baad 100644 --- a/src/Tgstation.Server.Api/Models/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/CompileJob.cs @@ -14,5 +14,10 @@ namespace Tgstation.Server.Api.Models /// Git revision the compiler ran on. Not modifiable /// public RevisionInformation RevisionInformation { get; set; } + + /// + /// The the was made with + /// + public Version ByondVersion { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index 6255f1591c..7f717c121c 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -37,10 +37,5 @@ namespace Tgstation.Server.Api.Models.Internal /// Exit code of DM. If /// public int? ExitCode { get; set; } - - /// - /// The the was made with - /// - public Version ByondVersion { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index aaeb1204e3..5d3caa81d1 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -275,7 +275,7 @@ namespace Tgstation.Server.Host.Components bool ddVerified; using (var byondLock = byond.UseExecutables(null)) { - job.ByondVersion = byondLock.Version; + job.ByondVersion = byondLock.Version.ToString(); await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 696d51016c..bf472fcd33 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -141,7 +141,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false); try { - var byondLock = currentByondLock ?? byond.UseExecutables(dmbProvider.CompileJob.ByondVersion); + var byondLock = currentByondLock ?? byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion)); try { //more sanitization here cause it uses the same scheme @@ -189,7 +189,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatJsonTrackingContext = await chat.TrackJsons(basePath, reattachInformation.ChatChannelsJson, reattachInformation.ChatCommandsJson, cancellationToken).ConfigureAwait(false); try { - var byondLock = byond.UseExecutables(reattachInformation.Dmb.CompileJob.ByondVersion); + var byondLock = byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion)); try { var session = executor.AttachToDreamDaemon(reattachInformation.ProcessId, byondLock); diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 6245765298..1fbcc9f498 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models @@ -23,6 +24,11 @@ namespace Tgstation.Server.Host.Models [Required] public RevisionInformation RevisionInformation { get; set; } + /// + /// The the was made with in string form + /// + public string ByondVersion { get; set; } + /// public Api.Models.CompileJob ToApi() => new Api.Models.CompileJob { @@ -33,7 +39,8 @@ namespace Tgstation.Server.Host.Models Id = Id, Job = Job.ToApi(), Output = Output, - RevisionInformation = RevisionInformation.ToApi() + RevisionInformation = RevisionInformation.ToApi(), + ByondVersion = Version.Parse(ByondVersion) }; } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index ff966eba6f..1aa4170164 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -121,6 +121,7 @@ namespace Tgstation.Server.Host.Models var chatChannel = modelBuilder.Entity(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique(); + chatChannel.HasOne(x => x.ChatSettings).WithMany(x => x.Channels).HasForeignKey(x => x.ChatSettingsId).OnDelete(DeleteBehavior.Cascade); } /// diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 519d590b0d..6bc7d937d2 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -13,9 +13,8 @@ namespace Tgstation.Server.Host.Core.Tests public void ApplyUpdate(string updatePath) => throw new NotImplementedException(); public void RegisterForUpdate(Action action) => throw new NotImplementedException(); - - [TestMethod] - public async Task TestSuccessfulStartup() + + public static async Task TestSuccessfulStartup() { var dbName = Path.GetTempFileName(); try From cc8c0e20017fdf8e21aaa18d1e88bfe48e7b6bcb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 14:42:24 -0400 Subject: [PATCH 242/397] Actually appveyor must have a database, no way this would work otherwise --- tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 6bc7d937d2..980101a5ec 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -14,7 +14,8 @@ namespace Tgstation.Server.Host.Core.Tests public void RegisterForUpdate(Action action) => throw new NotImplementedException(); - public static async Task TestSuccessfulStartup() + [TestMethod] + public async Task TestSuccessfulStartup() { var dbName = Path.GetTempFileName(); try From 5cbc879bbc7a27f70c9cc36d8a5dcc965c58407b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 15:12:43 -0400 Subject: [PATCH 243/397] And it was using SQLite too, you have no excuse --- tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 980101a5ec..22833bacdb 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; @@ -22,6 +23,7 @@ namespace Tgstation.Server.Host.Core.Tests { using (var webHost = WebHost.CreateDefaultBuilder(new string[] { "Database:DatabaseType=Sqlite", "Database:ConnectionString=Data Source=" + dbName }) //force it to use sqlite .UseStartup() + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) { From 64ef419ed0e48be83af57428f5982a1f72956616 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 15:34:14 -0400 Subject: [PATCH 244/397] Uhh, wow I think the chat system is done (barring commands) --- .../Components/Chat/Commands/BoolConverter.cs | 20 +++++++++++++++++++ .../{Command.cs => BuiltinCommand.cs} | 6 ++++-- .../Components/Chat/Commands/CustomCommand.cs | 17 +++++++++++++--- .../Chat/{ => Commands}/ICommandFactory.cs | 3 +-- .../Chat/Commands/VersionCommand.cs | 2 +- 5 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/BoolConverter.cs rename src/Tgstation.Server.Host/Components/Chat/Commands/{Command.cs => BuiltinCommand.cs} (78%) rename src/Tgstation.Server.Host/Components/Chat/{ => Commands}/ICommandFactory.cs (78%) diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/BoolConverter.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/BoolConverter.cs new file mode 100644 index 0000000000..e3103e4298 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/BoolConverter.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using System; + +namespace Tgstation.Server.Host.Components.Chat.Commands +{ + /// + /// for decoding bools returned by BYOND + /// + sealed class BoolConverter : JsonConverter + { + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteValue(((bool)value) ? 1 : 0); + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => reader.Value.ToString() == "1"; + + /// + public override bool CanConvert(Type objectType) => objectType == typeof(bool); + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/BuiltinCommand.cs similarity index 78% rename from src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs rename to src/Tgstation.Server.Host/Components/Chat/Commands/BuiltinCommand.cs index 94d7456133..2b965fc957 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/Command.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/BuiltinCommand.cs @@ -3,8 +3,10 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components.Chat.Commands { - /// - public abstract class Command : ICommand + /// + /// s written in C# + /// + public abstract class BuiltinCommand : ICommand { /// public string Name { get; protected set; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs index 64ba61c72d..c3108af26f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Threading; using System.Threading.Tasks; @@ -7,8 +8,18 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// /// Represents a command made from DM code /// - public sealed class CustomCommand : Command + sealed class CustomCommand : ICommand { + /// + public string Name { get; set; } + + /// + public string HelpText { get; set; } + + /// + [JsonConverter(typeof(BoolConverter))] + public bool AdminOnly { get; set; } + /// /// The for the /// @@ -26,7 +37,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands } /// - public override Task Invoke(string arguments, User user, CancellationToken cancellationToken) + public Task Invoke(string arguments, User user, CancellationToken cancellationToken) { if (handler == null) throw new InvalidOperationException("SetHandler() has not been called!"); diff --git a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommandFactory.cs similarity index 78% rename from src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs rename to src/Tgstation.Server.Host/Components/Chat/Commands/ICommandFactory.cs index 15827975ac..5520126604 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ICommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ICommandFactory.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; -using Tgstation.Server.Host.Components.Chat.Commands; -namespace Tgstation.Server.Host.Components.Chat +namespace Tgstation.Server.Host.Components.Chat.Commands { /// /// Factory for built in s diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs index 62e7f8228c..74ea35ea27 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// /// to return the /// - sealed class VersionCommand : Command + sealed class VersionCommand : BuiltinCommand { /// /// The for the From 197bf8908bb5f89bfd6329c56d27c86e2c012d2f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 15:37:08 -0400 Subject: [PATCH 245/397] Adds the kek command Critical priority feature --- .../Components/Chat/Commands/CommandFactory.cs | 1 + .../Components/Chat/Commands/KekCommand.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs index fd1e731c5e..a9bd873534 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs @@ -24,6 +24,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// public IReadOnlyList GenerateCommands() => new List { + new KekCommand(), new VersionCommand(application) }; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs new file mode 100644 index 0000000000..d608190e22 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs @@ -0,0 +1,14 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.Chat.Commands +{ + /// + /// kek + /// + sealed class KekCommand : BuiltinCommand + { + /// + public override Task Invoke(string arguments, User user, CancellationToken cancellationToken) => Task.FromResult("kek"); + } +} From bc7bea0d31fe0b79b8758df9d267b86495dd649a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 15:44:07 -0400 Subject: [PATCH 246/397] Ignore post chat address punctuation --- src/Tgstation.Server.Host/Components/Chat/Chat.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 04f5c5ac68..7febaa1f3b 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -148,11 +148,17 @@ namespace Tgstation.Server.Host.Components.Chat { logger.LogTrace("Chat message: {0}. User (Note unconverted provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User)); - if (!(message.Content.StartsWith(CommonMention, StringComparison.InvariantCultureIgnoreCase) || message.Content.StartsWith(provider.BotMention, StringComparison.Ordinal))) + var splits = new List(message.Content.Split(' ')); + var address = splits[0]; + if (address.Length > 1 && (address[address.Length - 1] == ':' || address[address.Length - 1] == ',')) + address = address.Substring(0, address.Length - 1); + + address = address.ToUpperInvariant(); + + if (address != CommonMention.ToUpperInvariant() && address != provider.BotMention.ToUpperInvariant()) //no mention return; - var splits = new List(message.Content.Split(' ')); if (splits.Count == 1) { //just a mention From 25d7f93c01027c63201b77b0689758e9ebd3b174 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 15:47:53 -0400 Subject: [PATCH 247/397] Implement builtin commands in a saner fashion --- .../Chat/Commands/BuiltinCommand.cs | 23 ------------------- .../Components/Chat/Commands/KekCommand.cs | 18 +++++++++++++-- .../Chat/Commands/VersionCommand.cs | 13 +++++++++-- 3 files changed, 27 insertions(+), 27 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/BuiltinCommand.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/BuiltinCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/BuiltinCommand.cs deleted file mode 100644 index 2b965fc957..0000000000 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/BuiltinCommand.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Tgstation.Server.Host.Components.Chat.Commands -{ - /// - /// s written in C# - /// - public abstract class BuiltinCommand : ICommand - { - /// - public string Name { get; protected set; } - - /// - public string HelpText { get; protected set; } - - /// - public bool AdminOnly { get; protected set; } - - /// - public abstract Task Invoke(string arguments, User user, CancellationToken cancellationToken); - } -} diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs index d608190e22..7fa541ef37 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/KekCommand.cs @@ -6,9 +6,23 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// /// kek /// - sealed class KekCommand : BuiltinCommand + sealed class KekCommand : ICommand { + /// + /// kek + /// + const string Kek = "kek"; + /// - public override Task Invoke(string arguments, User user, CancellationToken cancellationToken) => Task.FromResult("kek"); + public string Name => Kek; + + /// + public string HelpText => Kek; + + /// + public bool AdminOnly => false; + + /// + public Task Invoke(string arguments, User user, CancellationToken cancellationToken) => Task.FromResult(Kek); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs index 74ea35ea27..13b5a68f74 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/VersionCommand.cs @@ -8,8 +8,17 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// /// to return the /// - sealed class VersionCommand : BuiltinCommand + sealed class VersionCommand : ICommand { + /// + public string Name => "version"; + + /// + public string HelpText => "Displays the tgstation server version"; + + /// + public bool AdminOnly => false; + /// /// The for the /// @@ -25,6 +34,6 @@ namespace Tgstation.Server.Host.Components.Chat.Commands } /// - public override Task Invoke(string arguments, User user, CancellationToken cancellationToken) => Task.FromResult(application.VersionString); + public Task Invoke(string arguments, User user, CancellationToken cancellationToken) => Task.FromResult(application.VersionString); } } From 5b60abeb513b0e0c398eb3a4be1f016ce61b8466 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 15:51:47 -0400 Subject: [PATCH 248/397] Dispose useless inactive server sooner --- .../Components/Watchdog/Watchdog.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 9d060f4063..9dc9559905 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -360,17 +360,16 @@ namespace Tgstation.Server.Host.Components.Watchdog case MonitorActivationReason.ActiveLaunchParametersUpdated: //replace the notification tcs here so that the next loop will read a fresh one activeParametersUpdated = new TaskCompletionSource(); + monitorState.InactiveServer.Dispose(); //kill or recycle it + monitorState.NextAction = MonitorAction.Continue; - using (monitorState.InactiveServer) //it's dead, dispose it when we're done + var usedLatestDmb = await RestartInactiveServer().ConfigureAwait(false); + + if (monitorState.NextAction == MonitorAction.Continue) { - monitorState.NextAction = MonitorAction.Continue; - var usedLatestDmb = await RestartInactiveServer().ConfigureAwait(false); - if (monitorState.NextAction == MonitorAction.Continue) - { - monitorState.ActiveServer.ClosePortOnReboot = false; - if (monitorState.InactiveServerHasStagedDmb && !usedLatestDmb) - monitorState.InactiveServerHasStagedDmb = false; //don't try to load it again though - } + monitorState.ActiveServer.ClosePortOnReboot = false; + if (monitorState.InactiveServerHasStagedDmb && !usedLatestDmb) + monitorState.InactiveServerHasStagedDmb = false; //don't try to load it again though } break; case MonitorActivationReason.ActiveServerRebooted: From 63425da29811aafec60500c8d5df2404fbdc63ab Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 16:17:28 -0400 Subject: [PATCH 249/397] We'll never know if it works till we test it --- .../Components/Watchdog/MonitorAction.cs | 1 + .../Watchdog/MonitorActivationReason.cs | 2 +- .../Components/Watchdog/Watchdog.cs | 79 +++++++++---------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs index b5178c7b55..1e0199c88a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorAction.cs @@ -7,6 +7,7 @@ { Continue, Restart, + Break, Exit } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs index 45349a4be0..bfe0183058 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs @@ -6,8 +6,8 @@ InactiveServerCrashed, ActiveServerRebooted, InactiveServerRebooted, - NewDmbAvailable, InactiveServerStartupComplete, + NewDmbAvailable, ActiveLaunchParametersUpdated } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 9dc9559905..217d6dcddf 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -330,6 +330,23 @@ namespace Tgstation.Server.Host.Components.Watchdog return usedMostRecentDmb; } + async Task UpdateAndRestartInactiveServer(bool breakAfter) + { + //replace the notification tcs here so that the next loop will read a fresh one + activeParametersUpdated = new TaskCompletionSource(); + monitorState.InactiveServer.Dispose(); //kill or recycle it + monitorState.NextAction = breakAfter ? MonitorAction.Break : MonitorAction.Continue; + + var usedLatestDmb = await RestartInactiveServer().ConfigureAwait(false); + + if (monitorState.NextAction == (breakAfter ? MonitorAction.Break : MonitorAction.Continue)) + { + monitorState.ActiveServer.ClosePortOnReboot = false; + if (monitorState.InactiveServerHasStagedDmb && !usedLatestDmb) + monitorState.InactiveServerHasStagedDmb = false; //don't try to load it again though + } + }; + //reason handling switch (activationReason) { @@ -350,27 +367,13 @@ namespace Tgstation.Server.Host.Components.Watchdog await chat.SendWatchdogMessage("Active server crashed or exited! Onlining inactive server...", cancellationToken).ConfigureAwait(false); if (!await MakeInactiveActive().ConfigureAwait(false)) break; - - monitorState.NextAction = MonitorAction.Continue; + monitorState.ActiveServer.ClosePortOnReboot = false; - goto case MonitorActivationReason.ActiveLaunchParametersUpdated; + await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); + break; case MonitorActivationReason.InactiveServerCrashed: await chat.SendWatchdogMessage("Inactive server crashed or exited! Rebooting...", cancellationToken).ConfigureAwait(false); - goto case MonitorActivationReason.ActiveLaunchParametersUpdated; - case MonitorActivationReason.ActiveLaunchParametersUpdated: - //replace the notification tcs here so that the next loop will read a fresh one - activeParametersUpdated = new TaskCompletionSource(); - monitorState.InactiveServer.Dispose(); //kill or recycle it - monitorState.NextAction = MonitorAction.Continue; - - var usedLatestDmb = await RestartInactiveServer().ConfigureAwait(false); - - if (monitorState.NextAction == MonitorAction.Continue) - { - monitorState.ActiveServer.ClosePortOnReboot = false; - if (monitorState.InactiveServerHasStagedDmb && !usedLatestDmb) - monitorState.InactiveServerHasStagedDmb = false; //don't try to load it again though - } + await UpdateAndRestartInactiveServer(false).ConfigureAwait(false); break; case MonitorActivationReason.ActiveServerRebooted: if (FullRestartDeadInactive()) @@ -381,13 +384,6 @@ namespace Tgstation.Server.Host.Components.Watchdog var rebootState = monitorState.ActiveServer.RebootState; monitorState.ActiveServer.ResetRebootState(); //the DMAPI has already done this internally - /* TODO: This should be handled when ActiveLaunchParameters is SET - - if(LastLaunchParameters != ActiveLaunchParameters && rebootState != Components.Watchdog.RebootState.Shutdown) - //they need a relaunch with active parameters - rebootState = Components.Watchdog.RebootState.Restart; - */ - switch (rebootState) { case Components.Watchdog.RebootState.Normal: @@ -403,15 +399,13 @@ namespace Tgstation.Server.Host.Components.Watchdog return; } - if (monitorState.InactiveServerHasStagedDmb) - { - if (monitorState.InactiveServer.Dmb.CompileJob.Id == monitorState.ActiveServer.Dmb.CompileJob.Id) - //both servers up to date - monitorState.InactiveServerHasStagedDmb = false; - else - //need to load a new dmb in ActiveServer - restartOnceSwapped = true; - } + var sameCompileJob = monitorState.InactiveServer.Dmb.CompileJob.Id == monitorState.ActiveServer.Dmb.CompileJob.Id; + if (sameCompileJob && monitorState.InactiveServerHasStagedDmb) + //both servers up to date + monitorState.InactiveServerHasStagedDmb = false; + if (!sameCompileJob || ActiveLaunchParameters != LastLaunchParameters) + //need a new launch in ActiveServer + restartOnceSwapped = true; if (!await MakeInactiveActive().ConfigureAwait(false)) break; @@ -421,16 +415,16 @@ namespace Tgstation.Server.Host.Components.Watchdog //failing that, just reboot it restartOnceSwapped = !await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false); - if (restartOnceSwapped) //for one reason or another, - { - monitorState.InactiveServer.Dispose(); - monitorState.InactiveServerHasStagedDmb = await RestartInactiveServer().ConfigureAwait(false); - } + if (restartOnceSwapped) //for one reason or another, + await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); //break because worse case, active server is still booting + else + monitorState.NextAction = MonitorAction.Break; break; case MonitorActivationReason.InactiveServerRebooted: //should never happen but okay logger.LogWarning("Inactive server rebooted, this is a bug in DM code!"); monitorState.RebootingInactiveServer = true; + monitorState.InactiveServer.ResetRebootState(); //the DMAPI has already done this internally monitorState.ActiveServer.ClosePortOnReboot = false; monitorState.NextAction = MonitorAction.Continue; break; @@ -441,7 +435,12 @@ namespace Tgstation.Server.Host.Components.Watchdog monitorState.NextAction = MonitorAction.Continue; break; case MonitorActivationReason.NewDmbAvailable: - throw new NotImplementedException(); + monitorState.InactiveServerHasStagedDmb = true; + await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); //next case does same thing + break; + case MonitorActivationReason.ActiveLaunchParametersUpdated: + await UpdateAndRestartInactiveServer(false).ConfigureAwait(false); + break; } } From 349797b82d774a93b4209581073bac94362e4198 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Thu, 19 Jul 2018 16:20:34 -0400 Subject: [PATCH 250/397] Fix das build --- src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs | 1 + .../Components/Chat/Commands/CustomCommand.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs index 5a7b05ae0e..82f5cfc72c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using System; +using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Chat diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs index c3108af26f..da23754a2d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands /// /// Represents a command made from DM code /// - sealed class CustomCommand : ICommand + public sealed class CustomCommand : ICommand { /// public string Name { get; set; } From 02080b2402613d8a3e578f56cb6aff2d5779fac2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 14:05:57 -0400 Subject: [PATCH 251/397] Add logging to the host watchdog --- src/Tgstation.Server.Host.Console/Program.cs | 35 ++++++++--- src/Tgstation.Server.Host.Service/Program.cs | 4 +- .../ServerService.cs | 37 ++++++++--- .../Tgstation.Server.Host.Service.csproj | 5 +- .../IWatchdogFactory.cs | 7 ++- .../Watchdog.cs | 62 ++++++++++++++----- .../WatchdogFactory.cs | 5 +- 7 files changed, 117 insertions(+), 38 deletions(-) diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index f7ab19864e..b407ddbcb1 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; +using System.Collections.Generic; +using System.Threading.Tasks; using Tgstation.Server.Host.Watchdog; namespace Tgstation.Server.Host.Console @@ -13,11 +16,29 @@ namespace Tgstation.Server.Host.Console /// internal static IWatchdogFactory WatchdogFactory { get; set; } = new WatchdogFactory(); - /// - /// Entrypoint for the application - /// - /// The arguments for the - /// A representing the running operation - internal static Task Main(string[] args) => WatchdogFactory.CreateWatchdog().RunAsync(args, default); + /// + /// Entrypoint for the application + /// + /// The arguments for the + /// A representing the running operation + internal static async Task Main(string[] args) + { + using (var loggerFactory = new LoggerFactory()) + { + var arguments = new List(args); + var trace = arguments.Remove("--trace-host-watchdog"); + var debug = arguments.Remove("--debug-host-watchdog"); + + loggerFactory.AddConsole(trace ? LogLevel.Trace : debug ? LogLevel.Debug : LogLevel.Information, true); + + if (trace && debug) + { + loggerFactory.CreateLogger(nameof(Program)).LogCritical("Please specify only 1 of --trace-host-watchdog or --debug-host-watchdog!"); + return; + } + //default CancellationToken because the Host handles that internally + await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), default).ConfigureAwait(false); + } + } } } diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 1a5a1a9dcf..742b7e7ac6 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -1,4 +1,5 @@ using McMaster.Extensions.CommandLineUtils; +using Microsoft.Extensions.Logging; using System; using System.Collections.Specialized; using System.Configuration.Install; @@ -97,7 +98,8 @@ namespace Tgstation.Server.Host.Service installer.Uninstall(null); } else - ServiceBase.Run(new ServerService(new WatchdogFactory())); + using (var loggerFactory = new LoggerFactory()) + ServiceBase.Run(new ServerService(new WatchdogFactory(), loggerFactory)); } /// diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs index 1242ea88fc..97ff6944ea 100644 --- a/src/Tgstation.Server.Host.Service/ServerService.cs +++ b/src/Tgstation.Server.Host.Service/ServerService.cs @@ -1,4 +1,8 @@ -using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.EventLog; +using Microsoft.Extensions.Logging.EventLog.Internal; +using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.ServiceProcess; using System.Threading; @@ -10,7 +14,7 @@ namespace Tgstation.Server.Host.Service /// /// Represents a as a /// - sealed class ServerService : ServiceBase + sealed class ServerService : ServiceBase, IEventLog { /// /// The canonical windows service name @@ -20,7 +24,7 @@ namespace Tgstation.Server.Host.Service /// /// The for the /// - IWatchdog watchdog; + readonly IWatchdog watchdog; /// /// The recieved from of @@ -32,18 +36,33 @@ namespace Tgstation.Server.Host.Service /// CancellationTokenSource cancellationTokenSource; - /// - /// Construct a - /// - /// The to create with - public ServerService(IWatchdogFactory watchdogFactory) + /// + /// Construct a + /// + /// The to create with + /// The for + public ServerService(IWatchdogFactory watchdogFactory, ILoggerFactory loggerFactory) { if (watchdogFactory == null) throw new ArgumentNullException(nameof(watchdogFactory)); + if(loggerFactory == null) + throw new ArgumentNullException(nameof(loggerFactory)); + + loggerFactory.AddEventLog(new EventLogSettings + { + EventLog = this + }); + ServiceName = Name; - watchdog = watchdogFactory.CreateWatchdog(); + watchdog = watchdogFactory.CreateWatchdog(loggerFactory); } + /// + public int MaxMessageSize => (int)EventLog.MaximumKilobytes * 1024; + + /// + public void WriteEntry(string message, EventLogEntryType type, int eventID, short category) => EventLog.WriteEntry(message, type, eventID, category); + /// [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "cancellationTokenSource")] protected override void Dispose(bool disposing) diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index 2ea140835e..ed7a2783e3 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -90,7 +90,10 @@ - 2.2.4 + 2.2.5 + + + 2.1.1 diff --git a/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs index a1b203761a..9534709a2f 100644 --- a/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IWatchdogFactory.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Host.Watchdog +using Microsoft.Extensions.Logging; + +namespace Tgstation.Server.Host.Watchdog { /// /// Factory for creating s @@ -8,7 +10,8 @@ /// /// Create a /// + /// The to use for error reporting /// A new - IWatchdog CreateWatchdog(); + IWatchdog CreateWatchdog(ILoggerFactory loggerFactory); } } diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index ef74711534..e4c3fc6a74 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -24,38 +25,67 @@ namespace Tgstation.Server.Host.Watchdog /// readonly IIsolatedAssemblyContextFactory isolatedAssemblyLoader; + /// + /// The for the + /// + readonly ILogger logger; + /// /// Construct a /// /// The value of /// The value of /// The value of - public Watchdog(IServerFactory initialServerFactory, IActiveAssemblyDeleter activeAssemblyDeleter, IIsolatedAssemblyContextFactory isolatedAssemblyLoader) + /// The value of + public Watchdog(IServerFactory initialServerFactory, IActiveAssemblyDeleter activeAssemblyDeleter, IIsolatedAssemblyContextFactory isolatedAssemblyLoader, ILogger logger) { this.initialServerFactory = initialServerFactory ?? throw new ArgumentNullException(nameof(initialServerFactory)); this.activeAssemblyDeleter = activeAssemblyDeleter ?? throw new ArgumentNullException(nameof(activeAssemblyDeleter)); this.isolatedAssemblyLoader = isolatedAssemblyLoader ?? throw new ArgumentNullException(nameof(isolatedAssemblyLoader)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// public async Task RunAsync(string[] args, CancellationToken cancellationToken) { - //first run the host we started with - var serverFactory = initialServerFactory; - var assemblyPath = serverFactory.GetType().Assembly.Location; - do - { - var server = serverFactory.CreateServer(args); - await server.RunAsync(cancellationToken).ConfigureAwait(false); + logger.LogInformation("Host watchdog starting..."); + try + { + //first run the host we started with + logger.LogTrace("Running with initial server factory..."); + var serverFactory = initialServerFactory; + logger.LogTrace("Determining location of host assembly..."); + var assemblyPath = serverFactory.GetType().Assembly.Location; + logger.LogDebug("Path to initial host assembly: {0}", assemblyPath); + do + using (logger.BeginScope("Host invocation")) + { + var server = serverFactory.CreateServer(args); + logger.LogTrace("Running server..."); + await server.RunAsync(cancellationToken).ConfigureAwait(false); + logger.LogInformation("Active host exited."); - if (server.UpdatePath == null) - break; + if (server.UpdatePath == null) + break; - activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath); - File.Move(server.UpdatePath, assemblyPath); - serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath); - } - while (!cancellationToken.IsCancellationRequested); + logger.LogInformation("Update path is set to \"{0}\", attempting host assembly hotswap...", server.UpdatePath); + activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath); + logger.LogTrace("Moving new host assembly in place..."); + File.Move(server.UpdatePath, assemblyPath); + logger.LogTrace("Atttempting to create new server factory..."); + serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath); + } + while (!cancellationToken.IsCancellationRequested); + } + catch (OperationCanceledException) + { + logger.LogDebug("Exiting due to cancellation..."); + } + catch (Exception e) + { + logger.LogCritical("Error running host assembly! Exception: {0}", e); + } + logger.LogInformation("Host watchdog exiting..."); } } } diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs index ddb00dafe0..436e051429 100644 --- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Logging; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace Tgstation.Server.Host.Watchdog @@ -8,6 +9,6 @@ namespace Tgstation.Server.Host.Watchdog { /// [ExcludeFromCodeCoverage] - public IWatchdog CreateWatchdog() => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory()); + public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory(), loggerFactory.CreateLogger()); } } From 729c36ff6797df6be081e78fb609c43af205a1d4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 14:22:23 -0400 Subject: [PATCH 252/397] Fix up some things with efc logging --- .../Models/DatabaseContext.cs | 18 +----------------- .../MySqlDesignTimeDbContextFactory.cs | 3 +-- .../SqlServerDesignTimeDbContextFactory.cs | 3 +-- .../SqliteDesignTimeDbContextFactory.cs | 3 +-- .../Models/MySqlDatabaseContext.cs | 4 +--- .../Models/SqlServerDatabaseContext.cs | 4 +--- .../Models/SqliteDatabaseContext.cs | 4 +--- src/Tgstation.Server.Host/appsettings.json | 2 +- 8 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 1aa4170164..e92934f70e 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -1,10 +1,7 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; -#if !DEBUG using System.Linq; -#endif using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; @@ -83,10 +80,6 @@ namespace Tgstation.Server.Host.Models /// readonly DatabaseConfiguration databaseConfiguration; /// - /// The for the - /// - readonly ILoggerFactory loggerFactory; - /// /// The for the /// readonly IDatabaseSeeder databaseSeeder; @@ -96,12 +89,10 @@ namespace Tgstation.Server.Host.Models /// /// The for the /// The containing the value of - /// The value of /// The value of - public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions) + public DatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfigurationOptions, IDatabaseSeeder databaseSeeder) : base(dbContextOptions) { databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions)); - this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder)); } @@ -128,8 +119,6 @@ namespace Tgstation.Server.Host.Models protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - - optionsBuilder.UseLoggerFactory(loggerFactory); } /// @@ -147,14 +136,9 @@ namespace Tgstation.Server.Host.Models /// public async Task Initialize(CancellationToken cancellationToken) { -#if DEBUG - await Database.EnsureCreatedAsync().ConfigureAwait(false); - var wasEmpty = (await Users.CountAsync().ConfigureAwait(false)) == 0; -#else var migrations = await Database.GetAppliedMigrationsAsync().ConfigureAwait(false); var wasEmpty = !migrations.Any(); await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); -#endif if (wasEmpty) await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs index b33b088ed2..fd55e611c1 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDesignTimeDbContextFactory.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Logging; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models.Migrations @@ -10,6 +9,6 @@ namespace Tgstation.Server.Host.Models.Migrations sealed class MySqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory { /// - public MySqlDatabaseContext CreateDbContext(string[] args) => new MySqlDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); + public MySqlDatabaseContext CreateDbContext(string[] args) => new MySqlDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); } } diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs index 935768080b..75a1b97053 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDesignTimeDbContextFactory.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Logging; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models.Migrations @@ -10,6 +9,6 @@ namespace Tgstation.Server.Host.Models.Migrations sealed class SqlServerDesignTimeDbContextFactory : IDesignTimeDbContextFactory { /// - public SqlServerDatabaseContext CreateDbContext(string[] args) => new SqlServerDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); + public SqlServerDatabaseContext CreateDbContext(string[] args) => new SqlServerDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); } } diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs index 637bebfd0f..ece917a6b1 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Logging; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models.Migrations @@ -10,6 +9,6 @@ namespace Tgstation.Server.Host.Models.Migrations sealed class SqliteDesignTimeDbContextFactory : IDesignTimeDbContextFactory { /// - public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); + public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); } } diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs index e88678bf23..0f70b29fcd 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Tgstation.Server.Host.Configuration; @@ -15,9 +14,8 @@ namespace Tgstation.Server.Host.Models /// /// The for the /// The of for the - /// The for the /// The for the - public MySqlDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) + public MySqlDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, databaseSeeder) { } /// diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs index 240f8ba1e7..19f6736cc9 100644 --- a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Tgstation.Server.Host.Configuration; @@ -15,9 +14,8 @@ namespace Tgstation.Server.Host.Models /// /// The for the /// The of for the - /// The for the /// The for the - public SqlServerDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) + public SqlServerDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, databaseSeeder) { } /// diff --git a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs index 0d6c1c94e3..57885f1e56 100644 --- a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Tgstation.Server.Host.Configuration; @@ -15,9 +14,8 @@ namespace Tgstation.Server.Host.Models /// /// The for the /// The of for the - /// The for the /// The for the - public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder) + public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, databaseSeeder) { } /// diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 12aa0e9cf9..f62fafa2f2 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -13,7 +13,7 @@ }, "EntityFramework": { "LogLevel": { - "Default": "Warning" + "Default": "Information" } } }, From c008acc4c984fd98d49d091d1825259f9b98f0cd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 14:53:30 -0400 Subject: [PATCH 253/397] Properly stop logspam --- src/Tgstation.Server.Host/Core/Application.cs | 1 - src/Tgstation.Server.Host/Models/DatabaseContext.cs | 7 +++++++ src/Tgstation.Server.Host/appsettings.json | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index ae4489d387..351cb649ec 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -15,7 +15,6 @@ using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Reflection; using Tgstation.Server.Host.Components; -using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index e92934f70e..c75a8d9769 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -1,7 +1,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using System; +#if !DEBUG using System.Linq; +#endif using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; @@ -136,9 +138,14 @@ namespace Tgstation.Server.Host.Models /// public async Task Initialize(CancellationToken cancellationToken) { +#if DEBUG + await Database.EnsureCreatedAsync().ConfigureAwait(false); + var wasEmpty = (await Users.CountAsync().ConfigureAwait(false)) == 0; +#else var migrations = await Database.GetAppliedMigrationsAsync().ConfigureAwait(false); var wasEmpty = !migrations.Any(); await Database.MigrateAsync(cancellationToken).ConfigureAwait(false); +#endif if (wasEmpty) await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index f62fafa2f2..233a6dd6fa 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -8,6 +8,7 @@ }, "Console": { "LogLevel": { + "Microsoft.EntityFrameworkCore": "Warning", "Default": "Trace" } }, From 0b71a1be77664a6e24edf0ed6594e859a063f924 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 14:58:25 -0400 Subject: [PATCH 254/397] Cleans up a couple controller things --- src/Tgstation.Server.Host/Controllers/ChatController.cs | 6 +++++- src/Tgstation.Server.Host/Controllers/HomeController.cs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 2a19a797e3..71ba769ed8 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -20,7 +20,6 @@ namespace Tgstation.Server.Host.Controllers /// /// for managing /// - [TgsAuthorize] public sealed class ChatController : ModelController { /// @@ -39,6 +38,11 @@ namespace Tgstation.Server.Host.Controllers this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); } + /// + /// Converts to a + /// + /// The + /// A based on static Models.ChatChannel ConvertApiChatChannel(Api.Models.ChatChannel api) => new Models.ChatChannel { DiscordChannelId = api.DiscordChannelId, diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 7854609a1a..8abb2ca69f 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize] [HttpGet] - public JsonResult Home() => Json(application.Version); + public JsonResult Home() => Json(new { application.Version }); /// /// Attempt to authenticate a using From c6808fbb3d6604d9e1977e1e69896c4732b1ab14 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 14:59:44 -0400 Subject: [PATCH 255/397] Adds missing ChatController route --- src/Tgstation.Server.Host/Controllers/ChatController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 71ba769ed8..280e99cb08 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -20,6 +20,7 @@ namespace Tgstation.Server.Host.Controllers /// /// for managing /// + [Route("/" + nameof(Components.Chat.Chat))] public sealed class ChatController : ModelController { /// From 3272d3a7d764970f921a569d08aa63e51beb69d3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 15:03:07 -0400 Subject: [PATCH 256/397] Empty UsersController --- .../Controllers/UsersController.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/Tgstation.Server.Host/Controllers/UsersController.cs diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs new file mode 100644 index 0000000000..cd39e2e92f --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// For managing s + /// + [Route("/" + nameof(Models.User))] + public sealed class UsersController : ModelController + { + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// + public UsersController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + } +} From 1701f318e5d1fdb6f8fa4819517377b4cba15600 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 15:31:41 -0400 Subject: [PATCH 257/397] UsersController.Create --- .../Models/InstanceUser.cs | 6 ++ .../Models/Internal/User.cs | 1 - src/Tgstation.Server.Api/Models/User.cs | 2 +- src/Tgstation.Server.Api/Models/UserUpdate.cs | 16 ++++ .../Controllers/UsersController.cs | 78 ++++++++++++++++++- .../Models/DatabaseContext.cs | 1 + 6 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/UserUpdate.cs diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 2173fc2681..5cbcd6cbbb 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -8,6 +8,12 @@ namespace Tgstation.Server.Api.Models [Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)] public class InstanceUser { + /// + /// The of the the belongs to + /// + [Permissions(DenyWrite = true)] + public long UserId { get; set; } + /// /// The of the /// diff --git a/src/Tgstation.Server.Api/Models/Internal/User.cs b/src/Tgstation.Server.Api/Models/Internal/User.cs index e8c137c04a..d71512dbbe 100644 --- a/src/Tgstation.Server.Api/Models/Internal/User.cs +++ b/src/Tgstation.Server.Api/Models/Internal/User.cs @@ -19,7 +19,6 @@ namespace Tgstation.Server.Api.Models.Internal /// /// If the is enabled since users cannot be deleted. System users cannot be disabled /// - [Permissions(WriteRight = AdministrationRights.EditUsers)] public bool Enabled { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index 83fac4e82f..a18856a366 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -1,7 +1,7 @@ namespace Tgstation.Server.Api.Models { /// - public sealed class User : Internal.User + public class User : Internal.User { /// /// The who created this diff --git a/src/Tgstation.Server.Api/Models/UserUpdate.cs b/src/Tgstation.Server.Api/Models/UserUpdate.cs new file mode 100644 index 0000000000..f5f7eda6fc --- /dev/null +++ b/src/Tgstation.Server.Api/Models/UserUpdate.cs @@ -0,0 +1,16 @@ +using Tgstation.Server.Api.Rights; + +namespace Tgstation.Server.Api.Models +{ + /// + /// For editing a given . Will never be returned by the API + /// + public sealed class UserUpdate : User + { + /// + /// Cleartext password of the + /// + [Permissions(WriteRight = AdministrationRights.EditUsers)] + public string Password { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs index cd39e2e92f..4e71f62465 100644 --- a/src/Tgstation.Server.Host/Controllers/UsersController.cs +++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs @@ -1,6 +1,11 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -10,8 +15,18 @@ namespace Tgstation.Server.Host.Controllers /// For managing s /// [Route("/" + nameof(Models.User))] - public sealed class UsersController : ModelController + public sealed class UsersController : ModelController { + /// + /// The for the + /// + readonly ISystemIdentityFactory systemIdentityFactory; + + /// + /// The for the + /// + readonly ICryptographySuite cryptographySuite; + /// /// The for the /// @@ -22,10 +37,67 @@ namespace Tgstation.Server.Host.Controllers /// /// The for the /// The for the - /// - public UsersController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory) + /// The value of + /// The value of + /// The value of + public UsersController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } + + /// + [TgsAuthorize(AdministrationRights.EditUsers)] + public override async Task Create([FromBody] UserUpdate model, CancellationToken cancellationToken) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + + if (model.Name == null) + return BadRequest(new { message = "Missing user name!" }); + + if (model.Password == null && model.SystemIdentifier == null) + return BadRequest(new { message = "User must have either a password or system identifier!" }); + + var dbUser = new Models.User + { + AdministrationRights = model.AdministrationRights, + CreatedAt = DateTimeOffset.Now, + CreatedBy = AuthenticationContext.User, + Enabled = model.Enabled, + InstanceManagerRights = model.InstanceManagerRights, + Name = model.Name, + SystemIdentifier = model.SystemIdentifier + }; + + if (model.SystemIdentifier != null) + using (var systemIdentity = systemIdentityFactory.CreateSystemIdentity(dbUser)) + { + if (systemIdentity == null) + return Forbid(); + } + else + cryptographySuite.SetUserPassword(dbUser, model.Password); + + DatabaseContext.Users.Add(dbUser); + + try + { + try + { + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogInformation("Error creating user: {0}", e); + throw; + } + } + catch (DbUpdateConcurrencyException) + { + return Conflict(); + } + + return Ok(); + } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index c75a8d9769..14d049bb59 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -111,6 +111,7 @@ namespace Tgstation.Server.Host.Models var user = modelBuilder.Entity(); user.HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique(); user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict); + var chatChannel = modelBuilder.Entity(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique(); From 1af9644d73bf8f2ecce406ffa7fb4db0db2251f6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 16:43:48 -0400 Subject: [PATCH 258/397] More user stuff, other fixes --- .../Models/InstanceUser.cs | 21 ++++--- .../Models/Internal/User.cs | 14 ++--- src/Tgstation.Server.Api/Models/UserUpdate.cs | 5 +- .../WatchdogFactory.cs | 3 +- .../Controllers/ChatController.cs | 10 +++- .../Controllers/HomeController.cs | 2 +- .../Controllers/UsersController.cs | 42 ++++++++++++-- .../Models/DatabaseContext.cs | 2 + .../Models/InstanceUser.cs | 11 ++-- .../Security/AuthenticationContext.cs | 9 ++- .../TestProgram.cs | 9 ++- .../TestServerService.cs | 14 +++-- ...Tgstation.Server.Host.Service.Tests.csproj | 57 +++++++++++++++++++ .../packages.config | 17 ++++++ .../Security/TestAuthenticationContext.cs | 2 - .../TestWatchdog.cs | 19 ++++--- .../TestWatchdogFactory.cs | 5 +- 17 files changed, 190 insertions(+), 52 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 5cbcd6cbbb..b198ceff64 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Rights; +using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { @@ -17,31 +18,37 @@ namespace Tgstation.Server.Api.Models /// /// The of the /// - public ByondRights ByondRights { get; set; } + [Required] + public ByondRights? ByondRights { get; set; } /// /// The of the /// - public DreamDaemonRights DreamDaemonRights { get; set; } + [Required] + public DreamDaemonRights? DreamDaemonRights { get; set; } /// /// The of the /// - public DreamMakerRights DreamMakerRights { get; set; } + [Required] + public DreamMakerRights? DreamMakerRights { get; set; } /// /// The of the /// - public RepositoryRights RepositoryRights { get; set; } + [Required] + public RepositoryRights? RepositoryRights { get; set; } /// /// The of the /// - public ChatSettingsRights ChatSettingsRights { get; set; } + [Required] + public ChatSettingsRights? ChatSettingsRights { get; set; } /// /// The of the /// - public ConfigurationRights ConfigurationRights { get; set; } + [Required] + public ConfigurationRights? ConfigurationRights { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/User.cs b/src/Tgstation.Server.Api/Models/Internal/User.cs index d71512dbbe..b70e868079 100644 --- a/src/Tgstation.Server.Api/Models/Internal/User.cs +++ b/src/Tgstation.Server.Api/Models/Internal/User.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// Represents a server /// - [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers, CanCrud = true)] + [Model(RightsType.Administration, WriteRight = Rights.AdministrationRights.EditUsers, CanCrud = true)] public class User { /// @@ -19,7 +19,8 @@ namespace Tgstation.Server.Api.Models.Internal /// /// If the is enabled since users cannot be deleted. System users cannot be disabled /// - public bool Enabled { get; set; } + [Required] + public bool? Enabled { get; set; } /// /// When the was created @@ -37,20 +38,19 @@ namespace Tgstation.Server.Api.Models.Internal /// /// The name of the /// - [Permissions(WriteRight = AdministrationRights.EditUsers)] [Required] public string Name { get; set; } /// /// The for the /// - [Permissions(WriteRight = AdministrationRights.EditUsers)] - public AdministrationRights AdministrationRights { get; set; } + [Required] + public AdministrationRights? AdministrationRights { get; set; } /// /// The for the /// - [Permissions(WriteRight = AdministrationRights.EditUsers)] - public InstanceManagerRights InstanceManagerRights { get; set; } + [Required] + public InstanceManagerRights? InstanceManagerRights { get; set; } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/UserUpdate.cs b/src/Tgstation.Server.Api/Models/UserUpdate.cs index f5f7eda6fc..4203beed1b 100644 --- a/src/Tgstation.Server.Api/Models/UserUpdate.cs +++ b/src/Tgstation.Server.Api/Models/UserUpdate.cs @@ -1,6 +1,4 @@ -using Tgstation.Server.Api.Rights; - -namespace Tgstation.Server.Api.Models +namespace Tgstation.Server.Api.Models { /// /// For editing a given . Will never be returned by the API @@ -10,7 +8,6 @@ namespace Tgstation.Server.Api.Models /// /// Cleartext password of the /// - [Permissions(WriteRight = AdministrationRights.EditUsers)] public string Password { get; set; } } } diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs index 436e051429..8e2ac94dce 100644 --- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; @@ -9,6 +10,6 @@ namespace Tgstation.Server.Host.Watchdog { /// [ExcludeFromCodeCoverage] - public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory(), loggerFactory.CreateLogger()); + public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory(), loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory))); } } diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 280e99cb08..e04fbd52db 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -83,7 +83,15 @@ namespace Tgstation.Server.Host.Controllers }; DatabaseContext.ChatSettings.Add(dbModel); DatabaseContext.ChatChannels.AddRange(dbModel.Channels); - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + + try + { + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + } + catch (DbUpdateConcurrencyException) + { + return Conflict(); + } try { diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 8abb2ca69f..40ea8a1e91 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -107,7 +107,7 @@ namespace Tgstation.Server.Host.Controllers return Unauthorized(); } - if (!user.Enabled) + if (!user.Enabled.Value) return Forbid(); return Json(tokenFactory.CreateToken(user)); diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs index 4e71f62465..f66ffe1e5c 100644 --- a/src/Tgstation.Server.Host/Controllers/UsersController.cs +++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs @@ -2,6 +2,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -43,6 +45,8 @@ namespace Tgstation.Server.Host.Controllers public UsersController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); + this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); } /// @@ -60,11 +64,11 @@ namespace Tgstation.Server.Host.Controllers var dbUser = new Models.User { - AdministrationRights = model.AdministrationRights, + AdministrationRights = model.AdministrationRights ?? AdministrationRights.None, CreatedAt = DateTimeOffset.Now, CreatedBy = AuthenticationContext.User, - Enabled = model.Enabled, - InstanceManagerRights = model.InstanceManagerRights, + Enabled = model.Enabled ?? false, + InstanceManagerRights = model.InstanceManagerRights ?? InstanceManagerRights.None, Name = model.Name, SystemIdentifier = model.SystemIdentifier }; @@ -97,7 +101,37 @@ namespace Tgstation.Server.Host.Controllers return Conflict(); } - return Ok(); + return Json(dbUser.ToApi()); + } + + /// + [TgsAuthorize(AdministrationRights.EditUsers)] + public override async Task Update([FromBody] UserUpdate model, CancellationToken cancellationToken) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + + var originalUser = await DatabaseContext.Users.Where(x => x.Id == model.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (originalUser == default) + return StatusCode((int)HttpStatusCode.Gone); + + if (model.Password != null) + { + if (originalUser.PasswordHash == null) + return BadRequest(new { message = "Cannot convert a system user to a password user!" }); + cryptographySuite.SetUserPassword(originalUser, model.Password); + } + else if(model.SystemIdentifier != originalUser.SystemIdentifier) + return BadRequest(new { message = "Cannot change a user's system identifier!" }); + + originalUser.Name = model.Name ?? originalUser.Name; + originalUser.InstanceManagerRights = model.InstanceManagerRights ?? originalUser.InstanceManagerRights; + originalUser.AdministrationRights = model.AdministrationRights ?? originalUser.AdministrationRights; + originalUser.Enabled = model.Enabled ?? originalUser.Enabled; + + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + + return Json(originalUser.ToApi()); } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 14d049bb59..25caf31b19 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -112,6 +112,8 @@ namespace Tgstation.Server.Host.Models user.HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique(); user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict); + modelBuilder.Entity().HasIndex(x => new { x.UserId, x.Instance }).IsUnique(); + var chatChannel = modelBuilder.Entity(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique(); diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 8e2f3b9ec5..37fc313e04 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Host.Models { @@ -20,10 +19,10 @@ namespace Tgstation.Server.Host.Models /// /// If the has any instance rights /// - public bool AnyRights => ByondRights != ByondRights.None || - ChatSettingsRights != ChatSettingsRights.None || - ConfigurationRights != ConfigurationRights.None || - DreamDaemonRights != DreamDaemonRights.None || - DreamMakerRights != DreamMakerRights.None; + public bool AnyRights => ByondRights != Api.Rights.ByondRights.None || + ChatSettingsRights != Api.Rights.ChatSettingsRights.None || + ConfigurationRights != Api.Rights.ConfigurationRights.None || + DreamDaemonRights != Api.Rights.DreamDaemonRights.None || + DreamMakerRights != Api.Rights.DreamMakerRights.None; } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index dab8b2e60a..5e03fbff4b 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -51,10 +51,15 @@ namespace Tgstation.Server.Host.Security // use the api versions because they're the ones that contain the actual properties var typeToCheck = isInstance ? typeof(InstanceUser) : typeof(User); - var prop = typeToCheck.GetProperties().Where(x => x.PropertyType == rightsEnum).First(); + var nullableType = typeof(Nullable<>); + var nullableRightsType = nullableType.MakeGenericType(rightsEnum); + + var prop = typeToCheck.GetProperties().Where(x => x.PropertyType == nullableRightsType).First(); var right = prop.GetMethod.Invoke(isInstance ? (object)InstanceUser : User, Array.Empty()); - + + if (right == null) + throw new InvalidOperationException("A user right was null!"); return (int)right; } } diff --git a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs index e016e73849..695d75a619 100644 --- a/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs +++ b/tests/Tgstation.Server.Host.Console.Tests/TestProgram.cs @@ -1,5 +1,7 @@ +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Watchdog; @@ -13,11 +15,12 @@ namespace Tgstation.Server.Host.Console.Tests public async Task TestProgramRuns() { var mockServer = new Mock(); - mockServer.Setup(x => x.RunAsync(null, It.IsAny())).Returns(Task.CompletedTask).Verifiable(); + var args = Array.Empty(); + mockServer.Setup(x => x.RunAsync(args, It.IsAny())).Returns(Task.CompletedTask).Verifiable(); var mockServerFactory = new Mock(); - mockServerFactory.Setup(x => x.CreateWatchdog()).Returns(mockServer.Object).Verifiable(); + mockServerFactory.Setup(x => x.CreateWatchdog(It.IsAny())).Returns(mockServer.Object).Verifiable(); Program.WatchdogFactory = mockServerFactory.Object; - await Program.Main(null).ConfigureAwait(false); + await Program.Main(args).ConfigureAwait(false); mockServer.VerifyAll(); mockServerFactory.VerifyAll(); } diff --git a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs index d15ad6f69e..f4127014b5 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs +++ b/tests/Tgstation.Server.Host.Service.Tests/TestServerService.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Reflection; @@ -17,9 +18,11 @@ namespace Tgstation.Server.Host.Service.Tests [TestMethod] public void TestConstructionAndDisposal() { - Assert.ThrowsException(() => new ServerService(null)); + Assert.ThrowsException(() => new ServerService(null, null)); var mockWatchdogFactory = new Mock(); - new ServerService(mockWatchdogFactory.Object).Dispose(); + Assert.ThrowsException(() => new ServerService(mockWatchdogFactory.Object, null)); + var mockLoggerFactory = new LoggerFactory(); + new ServerService(mockWatchdogFactory.Object, mockLoggerFactory).Dispose(); } [TestMethod] @@ -34,9 +37,10 @@ namespace Tgstation.Server.Host.Service.Tests CancellationToken cancellationToken; mockWatchdog.Setup(x => x.RunAsync(args, It.IsAny())).Callback((string[] _, CancellationToken token) => cancellationToken = token).Returns(Task.CompletedTask).Verifiable(); var mockWatchdogFactory = new Mock(); - mockWatchdogFactory.Setup(x => x.CreateWatchdog()).Returns(mockWatchdog.Object).Verifiable(); + var mockLoggerFactory = new LoggerFactory(); + mockWatchdogFactory.Setup(x => x.CreateWatchdog(mockLoggerFactory)).Returns(mockWatchdog.Object).Verifiable(); - using(var service = new ServerService(mockWatchdogFactory.Object)) + using(var service = new ServerService(mockWatchdogFactory.Object, mockLoggerFactory)) { onStart.Invoke(service, new object[] { args }); diff --git a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj index ccb09b4c3c..b44b760e2c 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj +++ b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj @@ -41,6 +41,33 @@ ..\..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll + + ..\..\packages\Microsoft.Extensions.Configuration.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll + + + ..\..\packages\Microsoft.Extensions.Configuration.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Configuration.Binder.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll + + + ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Logging.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.dll + + + ..\..\packages\Microsoft.Extensions.Logging.Abstractions.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Logging.EventLog.2.1.1\lib\net461\Microsoft.Extensions.Logging.EventLog.dll + + + ..\..\packages\Microsoft.Extensions.Options.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + ..\..\packages\Microsoft.Extensions.Primitives.2.1.1\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + ..\..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll @@ -51,12 +78,42 @@ ..\..\packages\Moq.4.8.2\lib\net45\Moq.dll + + ..\..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll + + + + + ..\..\packages\System.Diagnostics.EventLog.4.5.0\lib\net461\System.Diagnostics.EventLog.dll + + + + ..\..\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll + + + + + ..\..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll + + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\..\packages\System.Security.AccessControl.4.5.0\lib\net461\System.Security.AccessControl.dll + + + ..\..\packages\System.Security.Permissions.4.5.0\lib\net461\System.Security.Permissions.dll + + + ..\..\packages\System.Security.Principal.Windows.4.5.0\lib\net461\System.Security.Principal.Windows.dll + ..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + ..\..\packages\System.ValueTuple.4.4.0\lib\net47\System.ValueTuple.dll True diff --git a/tests/Tgstation.Server.Host.Service.Tests/packages.config b/tests/Tgstation.Server.Host.Service.Tests/packages.config index c4f8ed9897..8a479af186 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/packages.config +++ b/tests/Tgstation.Server.Host.Service.Tests/packages.config @@ -1,9 +1,26 @@  + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs index 980faf020a..aa125813de 100644 --- a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs +++ b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs @@ -45,9 +45,7 @@ namespace Tgstation.Server.Host.Security.Tests user.AdministrationRights = AdministrationRights.EditUsers; instanceUser.ByondRights = ByondRights.ChangeVersion | ByondRights.ReadInstalled; Assert.AreEqual((int)user.AdministrationRights, authContext.GetRight(RightsType.Administration)); - Assert.AreEqual((int)user.InstanceManagerRights, authContext.GetRight(RightsType.InstanceManager)); Assert.AreEqual((int)instanceUser.ByondRights, authContext.GetRight(RightsType.Byond)); - Assert.AreEqual((int)instanceUser.RepositoryRights, authContext.GetRight(RightsType.Repository)); } } } diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs index 5d74371f08..56ec75351a 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Threading; @@ -13,13 +14,15 @@ namespace Tgstation.Server.Host.Watchdog.Tests [TestMethod] public void TestConstruction() { - Assert.ThrowsException(() => new Watchdog(null, null, null)); + Assert.ThrowsException(() => new Watchdog(null, null, null, null)); var mockServerFactory = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, null, null)); + Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, null, null, null)); var mockActiveAssemblyDeleter = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, null)); + Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, null, null)); var mockIsolatedServerContextFactory = new Mock(); - var wd = new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object); + Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, null)); + var mockLogger = new LoggerFactory().CreateLogger(); + var wd = new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); } class MockServerFactory : IServerFactory @@ -36,8 +39,9 @@ namespace Tgstation.Server.Host.Watchdog.Tests var mockServerFactory = new MockServerFactory(mockServer.Object); var mockActiveAssemblyDeleter = new Mock(); var mockIsolatedServerContextFactory = new Mock(); + var mockLogger = new LoggerFactory().CreateLogger(); - var wd = new Watchdog(mockServerFactory, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object); + var wd = new Watchdog(mockServerFactory, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); using (var cts = new CancellationTokenSource()) { @@ -56,8 +60,9 @@ namespace Tgstation.Server.Host.Watchdog.Tests var mockActiveAssemblyDeleter = new Mock(); var mockIsolatedServerContextFactory = new Mock(); mockIsolatedServerContextFactory.Setup(x => x.CreateIsolatedServerFactory(GetType().Assembly.Location)).Returns(mockServerFactory).Verifiable(); + var mockLogger = new LoggerFactory().CreateLogger(); - var wd = new Watchdog(mockServerFactory, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object); + var wd = new Watchdog(mockServerFactory, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); using (var cts = new CancellationTokenSource()) { diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs index e043a0a838..18fb82abc4 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdogFactory.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace Tgstation.Server.Host.Watchdog.Tests @@ -13,7 +14,7 @@ namespace Tgstation.Server.Host.Watchdog.Tests public void TestCreateWatchdog() { var factory = new WatchdogFactory(); - Assert.IsNotNull(factory.CreateWatchdog()); + Assert.IsNotNull(factory.CreateWatchdog(new LoggerFactory())); } } } From b4f712d00ba034fd7aef90abaaee7fad3ab72656 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 17:21:08 -0400 Subject: [PATCH 259/397] Bunch more user stuff + admin password resetting --- .../Models/InstanceUser.cs | 8 +++- .../Models/Internal/ServerSettings.cs | 8 +--- .../Configuration/DatabaseConfiguration.cs | 5 +++ .../Controllers/HomeController.cs | 2 +- .../Controllers/UsersController.cs | 40 +++++++++++++---- .../Models/DatabaseContext.cs | 2 + .../Models/DatabaseSeeder.cs | 43 ++++++++++++++++--- .../Models/IDatabaseSeeder.cs | 12 +++++- .../Security/AuthenticationContext.cs | 2 +- .../Security/AuthenticationContextFactory.cs | 7 +-- .../Security/ISystemIdentityFactory.cs | 10 +++-- .../Security/SystemIdentityFactory.cs | 6 ++- src/Tgstation.Server.Host/appsettings.json | 1 + 13 files changed, 112 insertions(+), 34 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index b198ceff64..4e25521934 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -8,13 +8,19 @@ namespace Tgstation.Server.Api.Models /// [Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)] public class InstanceUser - { + { /// /// The of the the belongs to /// [Permissions(DenyWrite = true)] public long UserId { get; set; } + /// + /// The of the the belongs to + /// + [Permissions(DenyWrite = true)] + public long InstanceId { get; set; } + /// /// The of the /// diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs index f044801219..3ab52adef5 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs @@ -8,12 +8,6 @@ namespace Tgstation.Server.Api.Models.Internal [Model(RightsType.Administration)] public class ServerSettings { - /// - /// Use the specified Windows/POSIX authentication group to authorize users. Changing this may enable or disable s depending on how they were configured. Setting this to changes the authentication mode to database. - /// - [Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)] - public string SystemAuthenticationGroup { get; set; } - /// /// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information. /// @@ -21,7 +15,7 @@ namespace Tgstation.Server.Api.Models.Internal public bool EnableTelemetry { get; set; } /// - /// The git repository to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to upstream pulls will be disabled entirely + /// The git repository URL to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to upstream pulls will be disabled entirely. Should be https://github.com/tgstation/tgstation-server or a fork of it /// [Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)] public string UpstreamRepository { get; set; } diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index 4afebe14a5..a75d463a48 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -15,6 +15,11 @@ /// public DatabaseType DatabaseType { get; set; } + /// + /// If the admin user should be enabled and have it's password reset + /// + public bool ResetAdminPassword { get; set; } + /// /// The connection string for the database /// diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 40ea8a1e91..79d6f528c3 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -100,7 +100,7 @@ namespace Tgstation.Server.Host.Controllers else try { - var systemIdentity = systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password); + using (await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false)) { } } catch { diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs index f66ffe1e5c..4eaaa01e6b 100644 --- a/src/Tgstation.Server.Host/Controllers/UsersController.cs +++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; @@ -59,8 +60,8 @@ namespace Tgstation.Server.Host.Controllers if (model.Name == null) return BadRequest(new { message = "Missing user name!" }); - if (model.Password == null && model.SystemIdentifier == null) - return BadRequest(new { message = "User must have either a password or system identifier!" }); + if (!(model.Password == null ^ model.SystemIdentifier == null)) + return BadRequest(new { message = "User must have exactly one of either a password or system identifier!" }); var dbUser = new Models.User { @@ -69,15 +70,22 @@ namespace Tgstation.Server.Host.Controllers CreatedBy = AuthenticationContext.User, Enabled = model.Enabled ?? false, InstanceManagerRights = model.InstanceManagerRights ?? InstanceManagerRights.None, - Name = model.Name, - SystemIdentifier = model.SystemIdentifier +#pragma warning disable CA1308 // Normalize strings to uppercase + Name = model.Name.ToLowerInvariant(), +#pragma warning restore CA1308 // Normalize strings to uppercase + SystemIdentifier = model.SystemIdentifier, + InstanceUsers = new List() }; if (model.SystemIdentifier != null) - using (var systemIdentity = systemIdentityFactory.CreateSystemIdentity(dbUser)) + try { - if (systemIdentity == null) - return Forbid(); + using (await systemIdentityFactory.CreateSystemIdentity(dbUser, cancellationToken).ConfigureAwait(false)) { } + } + catch(Exception e) + { + logger.LogInformation("System identifier user creation failure for {0}. Exception: {1}", model.SystemIdentifier, e); + return Forbid(); } else cryptographySuite.SetUserPassword(dbUser, model.Password); @@ -121,10 +129,12 @@ namespace Tgstation.Server.Host.Controllers return BadRequest(new { message = "Cannot convert a system user to a password user!" }); cryptographySuite.SetUserPassword(originalUser, model.Password); } - else if(model.SystemIdentifier != originalUser.SystemIdentifier) + else if(model.SystemIdentifier != null && model.SystemIdentifier != originalUser.SystemIdentifier) return BadRequest(new { message = "Cannot change a user's system identifier!" }); - originalUser.Name = model.Name ?? originalUser.Name; + if (model.Name != null && model.Name != originalUser.SystemIdentifier) + return BadRequest(new { message = "Cannot change a user's name!" }); + originalUser.InstanceManagerRights = model.InstanceManagerRights ?? originalUser.InstanceManagerRights; originalUser.AdministrationRights = model.AdministrationRights ?? originalUser.AdministrationRights; originalUser.Enabled = model.Enabled ?? originalUser.Enabled; @@ -133,5 +143,17 @@ namespace Tgstation.Server.Host.Controllers return Json(originalUser.ToApi()); } + + /// + [TgsAuthorize] + public override Task Read(CancellationToken cancellationToken) => Task.FromResult(Json(AuthenticationContext.User.ToApi())); + + /// + [TgsAuthorize(AdministrationRights.EditUsers)] + public override async Task List(CancellationToken cancellationToken) + { + var users = await DatabaseContext.Users.ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(users); + } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 25caf31b19..04befa41fa 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -151,6 +151,8 @@ namespace Tgstation.Server.Host.Models #endif if (wasEmpty) await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); + else if(databaseConfiguration.ResetAdminPassword) + await databaseSeeder.ResetAdminPassword(this, cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index 7ccf4f33ca..ef57b6f6b6 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -1,4 +1,6 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Rights; @@ -10,7 +12,14 @@ namespace Tgstation.Server.Host.Models sealed class DatabaseSeeder : IDatabaseSeeder { /// - /// The default password mode admin password + /// The name of the default admin user + /// +#pragma warning disable CA1308 // Normalize strings to uppercase + static readonly string AdminName = "admin".ToLowerInvariant(); +#pragma warning restore CA1308 // Normalize strings to uppercase + + /// + /// The default admin password /// const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"; @@ -30,19 +39,28 @@ namespace Tgstation.Server.Host.Models /// The value of public DatabaseSeeder(ICryptographySuite cryptographySuite) => this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); - /// - public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken) + /// + /// Add a default admin to a given + /// + /// The to add an admin to + void SeedAdminUser(IDatabaseContext databaseContext) { var admin = new User { AdministrationRights = (AdministrationRights)~0, CreatedAt = DateTimeOffset.Now, InstanceManagerRights = (InstanceManagerRights)~0, - Name = "Admin", + Name = AdminName, Enabled = true, }; cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); databaseContext.Users.Add(admin); + } + + /// + public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken) + { + SeedAdminUser(databaseContext); var serverSettings = await databaseContext.GetServerSettings(cancellationToken).ConfigureAwait(false); @@ -51,5 +69,20 @@ namespace Tgstation.Server.Host.Models await databaseContext.Save(cancellationToken).ConfigureAwait(false); } + + /// + public async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken) + { + var admin = await databaseContext.Users.Where(x => x.Name == AdminName).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (admin == default) + SeedAdminUser(databaseContext); + else + { + admin.Enabled = true; + cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); + } + + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs index f57620fd0a..354b3eee7b 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Models /// For initially seeding a database /// interface IDatabaseSeeder - { + { /// /// Initially seed a given /// @@ -15,5 +15,13 @@ namespace Tgstation.Server.Host.Models /// The for the operation /// A representing the running operation Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken); - } + + /// + /// Changes the admin password in back to it's default and enables the account + /// + /// The to reset the admin password for + /// The for the operation + /// A representing the running operation + Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken); + } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index 5e03fbff4b..47d3cac17e 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Security } /// - public void Dispose() => SystemIdentity.Dispose(); + public void Dispose() => SystemIdentity?.Dispose(); /// public IAuthenticationContext Clone() => new AuthenticationContext(SystemIdentity.Clone(), User, InstanceUser); diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 7c7e3465c7..1b73c7afc5 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -45,13 +45,14 @@ namespace Tgstation.Server.Host.Security if (instanceId.HasValue) userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId)); - var user = await userQuery.FirstAsync(cancellationToken).ConfigureAwait(false); + var user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false); InstanceUser instanceUser = null; if (instanceId.HasValue) - instanceUser = user.InstanceUsers.First(); + instanceUser = user.InstanceUsers.Where(x => x.InstanceId == instanceId).First(); - CurrentAuthenticationContext = new AuthenticationContext(user.SystemIdentifier != null ? systemIdentityFactory.CreateSystemIdentity(user) : null, user, instanceUser); + var systemIdentity = user.SystemIdentifier != null ? await systemIdentityFactory.CreateSystemIdentity(user, cancellationToken).ConfigureAwait(false) : null; + CurrentAuthenticationContext = new AuthenticationContext(systemIdentity, user, instanceUser); } } } diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs index 86f4f9b7e2..11ba06ebe7 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs @@ -1,4 +1,6 @@ -using Tgstation.Server.Host.Models; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { @@ -11,15 +13,17 @@ namespace Tgstation.Server.Host.Security /// Create a for a given /// /// The user to create a for + /// The for the operation /// A new or if the has no - ISystemIdentity CreateSystemIdentity(User user); + Task CreateSystemIdentity(User user, CancellationToken cancellationToken); /// /// Create a for a given username and password /// /// The username of the user /// The password of the user + /// The for the operation /// A new - ISystemIdentity CreateSystemIdentity(string username, string password); + Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs index f9e9c15a87..4f384ff4b4 100644 --- a/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -7,13 +9,13 @@ namespace Tgstation.Server.Host.Security sealed class SystemIdentityFactory : ISystemIdentityFactory { /// - public ISystemIdentity CreateSystemIdentity(User user) + public Task CreateSystemIdentity(User user, CancellationToken cancellationToken) { throw new NotImplementedException(); } /// - public ISystemIdentity CreateSystemIdentity(string username, string password) + public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 233a6dd6fa..8abbd1eb6a 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -20,6 +20,7 @@ }, "Database": { "DatabaseType": "SqlServer", + "ResetAdminPassword": false, "ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True" } } From 6268da892b9a3e6c5a5365f7d1664a371e84bf6b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 17:31:32 -0400 Subject: [PATCH 260/397] Fixes and optimizations out the ass --- .../Controllers/HomeController.cs | 7 ++++--- .../Controllers/UsersController.cs | 20 ++++++++++++------- .../Models/DatabaseContext.cs | 3 ++- .../Models/DatabaseSeeder.cs | 7 +++---- src/Tgstation.Server.Host/Models/User.cs | 5 +++++ 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 79d6f528c3..a1849bfc86 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -69,8 +69,9 @@ namespace Tgstation.Server.Host.Controllers { if (ApiHeaders.IsTokenAuthentication) return BadRequest(new { message = "Cannot create a token using another token!" }); - - var user = await DatabaseContext.Users.Where(x => x.Name == ApiHeaders.Username).Select(x => new User{ + + var user = await DatabaseContext.Users.Where(x => x.CanonicalName == ApiHeaders.Username.ToUpperInvariant()).Select(x => new User + { Id = x.Id, PasswordHash = x.PasswordHash, SystemIdentifier = x.SystemIdentifier, @@ -100,7 +101,7 @@ namespace Tgstation.Server.Host.Controllers else try { - using (await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false)) { } + using (await systemIdentityFactory.CreateSystemIdentity(user.Name, ApiHeaders.Password, cancellationToken).ConfigureAwait(false)) { } } catch { diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs index 4eaaa01e6b..da5ccc9b9b 100644 --- a/src/Tgstation.Server.Host/Controllers/UsersController.cs +++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs @@ -57,12 +57,12 @@ namespace Tgstation.Server.Host.Controllers if (model == null) throw new ArgumentNullException(nameof(model)); - if (model.Name == null) - return BadRequest(new { message = "Missing user name!" }); - if (!(model.Password == null ^ model.SystemIdentifier == null)) return BadRequest(new { message = "User must have exactly one of either a password or system identifier!" }); + if (!(model.Name == null ^ model.SystemIdentifier == null)) + return BadRequest(new { message = "User must have a name if and only if user has no system identifier!" }); + var dbUser = new Models.User { AdministrationRights = model.AdministrationRights ?? AdministrationRights.None, @@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers Enabled = model.Enabled ?? false, InstanceManagerRights = model.InstanceManagerRights ?? InstanceManagerRights.None, #pragma warning disable CA1308 // Normalize strings to uppercase - Name = model.Name.ToLowerInvariant(), + Name = model.Name, #pragma warning restore CA1308 // Normalize strings to uppercase SystemIdentifier = model.SystemIdentifier, InstanceUsers = new List() @@ -80,7 +80,10 @@ namespace Tgstation.Server.Host.Controllers if (model.SystemIdentifier != null) try { - using (await systemIdentityFactory.CreateSystemIdentity(dbUser, cancellationToken).ConfigureAwait(false)) { } + using (var sysIdentity = await systemIdentityFactory.CreateSystemIdentity(dbUser, cancellationToken).ConfigureAwait(false)) + { + dbUser.Name = sysIdentity.Username; + } } catch(Exception e) { @@ -90,6 +93,8 @@ namespace Tgstation.Server.Host.Controllers else cryptographySuite.SetUserPassword(dbUser, model.Password); + dbUser.CanonicalName = dbUser.Name.ToUpperInvariant(); + DatabaseContext.Users.Add(dbUser); try @@ -132,12 +137,13 @@ namespace Tgstation.Server.Host.Controllers else if(model.SystemIdentifier != null && model.SystemIdentifier != originalUser.SystemIdentifier) return BadRequest(new { message = "Cannot change a user's system identifier!" }); - if (model.Name != null && model.Name != originalUser.SystemIdentifier) - return BadRequest(new { message = "Cannot change a user's name!" }); + if (model.Name != null && model.Name.ToUpperInvariant() != originalUser.CanonicalName) + return BadRequest(new { message = "Can only change capitalization of a user's name!" }); originalUser.InstanceManagerRights = model.InstanceManagerRights ?? originalUser.InstanceManagerRights; originalUser.AdministrationRights = model.AdministrationRights ?? originalUser.AdministrationRights; originalUser.Enabled = model.Enabled ?? originalUser.Enabled; + originalUser.Name = model.Name ?? originalUser.Name; await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 04befa41fa..8bb7a69218 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -109,7 +109,8 @@ namespace Tgstation.Server.Host.Models modelBuilder.Entity().HasIndex(x => x.Commit).IsUnique(); var user = modelBuilder.Entity(); - user.HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique(); + user.HasIndex(x => new { x.CanonicalName, x.SystemIdentifier }).IsUnique(); + user.HasIndex(x => x.CanonicalName).IsUnique(); user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity().HasIndex(x => new { x.UserId, x.Instance }).IsUnique(); diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index ef57b6f6b6..772368aed3 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -14,9 +14,7 @@ namespace Tgstation.Server.Host.Models /// /// The name of the default admin user /// -#pragma warning disable CA1308 // Normalize strings to uppercase - static readonly string AdminName = "admin".ToLowerInvariant(); -#pragma warning restore CA1308 // Normalize strings to uppercase + const string AdminName = "Admin"; /// /// The default admin password @@ -51,6 +49,7 @@ namespace Tgstation.Server.Host.Models CreatedAt = DateTimeOffset.Now, InstanceManagerRights = (InstanceManagerRights)~0, Name = AdminName, + CanonicalName = AdminName.ToUpperInvariant(), Enabled = true, }; cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); @@ -73,7 +72,7 @@ namespace Tgstation.Server.Host.Models /// public async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken) { - var admin = await databaseContext.Users.Where(x => x.Name == AdminName).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var admin = await databaseContext.Users.Where(x => x.CanonicalName == AdminName.ToUpperInvariant()).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (admin == default) SeedAdminUser(databaseContext); else diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index 93587647ef..eed7ccbba4 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -16,6 +16,11 @@ namespace Tgstation.Server.Host.Models /// public User CreatedBy { get; set; } + /// + /// The uppercase invariant of + /// + public string CanonicalName { get; set; } + /// /// s created by this /// From 12671f0247b5aa7aa646d65255cb3849389c3bee Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 17:35:28 -0400 Subject: [PATCH 261/397] For gods sakes man. NORMALIZE THE FUCKING TING --- src/Tgstation.Server.Host/Controllers/UsersController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs index da5ccc9b9b..bb13cec980 100644 --- a/src/Tgstation.Server.Host/Controllers/UsersController.cs +++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs @@ -83,6 +83,7 @@ namespace Tgstation.Server.Host.Controllers using (var sysIdentity = await systemIdentityFactory.CreateSystemIdentity(dbUser, cancellationToken).ConfigureAwait(false)) { dbUser.Name = sysIdentity.Username; + dbUser.SystemIdentifier = sysIdentity.Uid; } } catch(Exception e) From 751f86d186695be3d1f1223385f92caec1760038 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 17:39:58 -0400 Subject: [PATCH 262/397] Fix things --- src/Tgstation.Server.Api/Models/Administration.cs | 6 ------ .../Rights/AdministrationRights.cs | 10 +++------- src/Tgstation.Server.Host/Models/DatabaseContext.cs | 1 - src/Tgstation.Server.Host/Security/ISystemIdentity.cs | 5 ----- 4 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 7f04e2d428..640a5c65ac 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -24,11 +24,5 @@ namespace Tgstation.Server.Api.Models /// [Permissions(WriteRight = AdministrationRights.ChangeVersion)] public Version CurrentVersion { get; set; } - - /// - /// Users in the - /// - [Permissions(DenyWrite = true)] - public IReadOnlyList Users { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index bc7d302397..c8992b3575 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -13,9 +13,9 @@ namespace Tgstation.Server.Api.Rights /// None = 0, /// - /// User can change + /// User can change /// - ChangeAuthenticationGroup = 1, + SetUpstreamRepository = 1, /// /// User can change /// @@ -31,10 +31,6 @@ namespace Tgstation.Server.Api.Rights /// /// User can change /// - ChangeVersion = 16, - /// - /// User can change - /// - SetUpstreamRepository = 32 + ChangeVersion = 16 } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 8bb7a69218..5ca0997c1e 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -109,7 +109,6 @@ namespace Tgstation.Server.Host.Models modelBuilder.Entity().HasIndex(x => x.Commit).IsUnique(); var user = modelBuilder.Entity(); - user.HasIndex(x => new { x.CanonicalName, x.SystemIdentifier }).IsUnique(); user.HasIndex(x => x.CanonicalName).IsUnique(); user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict); diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs index ff2dc3482c..fecf36a60a 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs @@ -20,11 +20,6 @@ namespace Tgstation.Server.Host.Security /// string Username { get; } - /// - /// Groups the user is in - /// - IEnumerable Groups { get; } - /// /// Clone the creating another copy that must have called on it /// From 25c831a46cd836feb62fbf57a1ee067afb14b7b5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 11:06:30 -0400 Subject: [PATCH 263/397] Some controller fixups --- .../Controllers/ChatController.cs | 17 +++++++++++++++++ .../Controllers/UsersController.cs | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index e04fbd52db..20afd6691e 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -149,6 +149,23 @@ namespace Tgstation.Server.Host.Controllers return Json(results); } + /// + [TgsAuthorize(ChatSettingsRights.Read)] + public override async Task GetId(long id, CancellationToken cancellationToken) + { + var query = DatabaseContext.ChatSettings.Where(x => x.Id == id).Include(x => x.Channels); + + var results = await query.ToListAsync(cancellationToken).ConfigureAwait(false); + + var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatSettings) & (int)ChatSettingsRights.ReadConnectionString) != 0; + + if (!connectionStrings) + foreach (var I in results) + I.ConnectionString = null; + + return Json(results); + } + /// [TgsAuthorize(ChatSettingsRights.WriteChannels | ChatSettingsRights.WriteConnectionString | ChatSettingsRights.WriteEnabled | ChatSettingsRights.WriteName | ChatSettingsRights.WriteProvider)] public override async Task Update([FromBody] Api.Models.ChatSettings model, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs index bb13cec980..c7ccafa5d7 100644 --- a/src/Tgstation.Server.Host/Controllers/UsersController.cs +++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs @@ -162,5 +162,15 @@ namespace Tgstation.Server.Host.Controllers var users = await DatabaseContext.Users.ToListAsync(cancellationToken).ConfigureAwait(false); return Json(users); } + + /// + [TgsAuthorize(AdministrationRights.EditUsers)] + public override async Task GetId(long id, CancellationToken cancellationToken) + { + var user = await DatabaseContext.Users.Where(x => x.Id == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (user == default) + return NotFound(); + return Json(user); + } } } From a5e3f68cc211c71eb3539db1e964443e3f6f2b96 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 11:43:48 -0400 Subject: [PATCH 264/397] InstanceUserController --- .../Models/InstanceUser.cs | 6 +- .../Controllers/InstanceController.cs | 12 +- .../Controllers/InstanceUserController.cs | 161 ++++++++++++++++++ .../Controllers/JobController.cs | 2 +- .../{UsersController.cs => UserController.cs} | 22 +-- .../Models/DatabaseContext.cs | 4 +- src/Tgstation.Server.Host/Models/Instance.cs | 14 +- .../Models/InstanceUser.cs | 16 +- 8 files changed, 212 insertions(+), 25 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/InstanceUserController.cs rename src/Tgstation.Server.Host/Controllers/{UsersController.cs => UserController.cs} (89%) diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 4e25521934..58ed24033c 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -13,13 +13,15 @@ namespace Tgstation.Server.Api.Models /// The of the the belongs to /// [Permissions(DenyWrite = true)] - public long UserId { get; set; } + [Required] + public long? UserId { get; set; } /// /// The of the the belongs to /// [Permissions(DenyWrite = true)] - public long InstanceId { get; set; } + [Required] + public long? InstanceId { get; set; } /// /// The of the diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index d5665f6a4b..fb18f46897 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using System; using System.Linq; using System.Net; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Rights; @@ -63,7 +64,7 @@ namespace Tgstation.Server.Host.Controllers DreamMakerSettings = new DreamMakerSettings(), Name = model.Name, Online = false, - Path = model.Path, + Path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? model.Path.ToUpperInvariant() : model.Path, RepositorySettings = new RepositorySettings() }; @@ -91,15 +92,12 @@ namespace Tgstation.Server.Host.Controllers throw; } } - catch (Exception e) + catch (DbUpdateConcurrencyException e) { return Conflict(new { message = e.Message }); } - - model.Online = newInstance.Online; - model.Id = newInstance.Id; - - return Json(model); + + return Json(newInstance.ToApi()); } /// diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs new file mode 100644 index 0000000000..abe94e092c --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -0,0 +1,161 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; +using Z.EntityFramework.Plus; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// For managing s + /// + [Route("/" + nameof(Models.InstanceUser))] + public sealed class InstanceUserController : ModelController + { + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Checks a for errors + /// + /// The to check + /// A explaining any errors, if none + BadRequestObjectResult StandardModelChecks(Api.Models.InstanceUser model) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + + if (model.InstanceId.HasValue && model.InstanceId != Instance.Id) + return BadRequest(new { message = "InstanceId does not match headers!" }); + + if (!model.InstanceId.HasValue) + return BadRequest(new { message = "Missing UserId!" }); + + return null; + } + + /// + [TgsAuthorize(AdministrationRights.EditUsers)] + public override async Task Create([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken) + { + var test = StandardModelChecks(model); + if (test != null) + return test; + + var dbUser = new Models.InstanceUser + { + ByondRights = model.ByondRights ?? ByondRights.None, + ChatSettingsRights = model.ChatSettingsRights ?? ChatSettingsRights.None, + ConfigurationRights = model.ConfigurationRights ?? ConfigurationRights.None, + DreamDaemonRights = model.DreamDaemonRights ?? DreamDaemonRights.None, + DreamMakerRights = model.DreamMakerRights ?? DreamMakerRights.None, + RepositoryRights = model.RepositoryRights ?? RepositoryRights.None, + UserId = model.UserId + }; + + var ourInstance = new Models.Instance + { + Id = Instance.Id + }; + + DatabaseContext.Instances.Attach(ourInstance); + + ourInstance.InstanceUsers.Add(dbUser); + + try + { + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + } + catch (DbUpdateConcurrencyException e) + { + return Conflict(new { message = e.Message }); + } + return Json(dbUser.ToApi()); + } + + /// + [TgsAuthorize(AdministrationRights.EditUsers)] + public override async Task Update([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken) + { + var test = StandardModelChecks(model); + if (test != null) + return test; + + var originalUser = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == model.UserId).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (originalUser == null) + return StatusCode((int)HttpStatusCode.Gone); + + originalUser.ByondRights = model.ByondRights ?? originalUser.ByondRights; + originalUser.ChatSettingsRights = model.ChatSettingsRights ?? originalUser.ChatSettingsRights; + originalUser.ConfigurationRights = model.ConfigurationRights ?? originalUser.ConfigurationRights; + originalUser.DreamDaemonRights = model.DreamDaemonRights ?? originalUser.DreamDaemonRights; + originalUser.DreamMakerRights = model.DreamMakerRights ?? originalUser.DreamMakerRights; + + try + { + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + } + catch (DbUpdateConcurrencyException e) + { + return Conflict(new { message = e.Message }); + } + return Json(originalUser.ToApi()); + } + + /// + [TgsAuthorize] + public override Task Read(CancellationToken cancellationToken) => Task.FromResult(AuthenticationContext.InstanceUser != null ? (IActionResult)Json(AuthenticationContext.InstanceUser.ToApi()) : NotFound()); + + /// + [TgsAuthorize(InstanceUserRights.ReadUsers)] + public override async Task List(CancellationToken cancellationToken) + { + var users = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(users.Select(x => x.ToApi())); + } + + /// + [TgsAuthorize(InstanceUserRights.ReadUsers)] + public override async Task GetId(long id, CancellationToken cancellationToken) + { + //this functions as userId + var user = await DatabaseContext.Instances.Where(x => x.Id == id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (user == default) + return NotFound(); + return Json(user.ToApi()); + } + + /// + [TgsAuthorize(AdministrationRights.EditUsers)] + public override async Task Delete([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken) + { + var test = StandardModelChecks(model); + if (test != null) + return test; + + await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == model.UserId).DeleteAsync(cancellationToken).ConfigureAwait(false); + return Ok(); + } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index a84d50c8f5..cefec12234 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -40,7 +40,7 @@ namespace Tgstation.Server.Host.Controllers IQueryable query = DatabaseContext.Jobs; if (Instance != null) { - if (!AuthenticationContext.InstanceUser.AnyRights) + if (AuthenticationContext.InstanceUser?.AnyRights != true) return Forbid(); query = query.Where(x => x.Instance.Id == Instance.Id); } diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs similarity index 89% rename from src/Tgstation.Server.Host/Controllers/UsersController.cs rename to src/Tgstation.Server.Host/Controllers/UserController.cs index c7ccafa5d7..50be28d1dd 100644 --- a/src/Tgstation.Server.Host/Controllers/UsersController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -18,32 +18,32 @@ namespace Tgstation.Server.Host.Controllers /// For managing s /// [Route("/" + nameof(Models.User))] - public sealed class UsersController : ModelController + public sealed class UserController : ModelController { /// - /// The for the + /// The for the /// readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the + /// The for the /// readonly ICryptographySuite cryptographySuite; /// - /// The for the + /// The for the /// - readonly ILogger logger; + readonly ILogger logger; /// - /// Construct a + /// Construct a /// /// The for the /// The for the /// The value of /// The value of /// The value of - public UsersController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory) + public UserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); @@ -110,9 +110,9 @@ namespace Tgstation.Server.Host.Controllers throw; } } - catch (DbUpdateConcurrencyException) + catch (DbUpdateConcurrencyException e) { - return Conflict(); + return Conflict(new { message = e.Message }); } return Json(dbUser.ToApi()); @@ -160,7 +160,7 @@ namespace Tgstation.Server.Host.Controllers public override async Task List(CancellationToken cancellationToken) { var users = await DatabaseContext.Users.ToListAsync(cancellationToken).ConfigureAwait(false); - return Json(users); + return Json(users.Select(x => x.ToApi())); } /// @@ -170,7 +170,7 @@ namespace Tgstation.Server.Host.Controllers var user = await DatabaseContext.Users.Where(x => x.Id == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (user == default) return NotFound(); - return Json(user); + return Json(user.ToApi()); } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 5ca0997c1e..44099444d8 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -106,13 +106,13 @@ namespace Tgstation.Server.Host.Models // build default model. LogModelBuilderHelper.Build(modelBuilder.Entity()); modelBuilder.Entity().ToTable(nameof(Logs)); - + modelBuilder.Entity().HasIndex(x => x.Path).IsUnique(); modelBuilder.Entity().HasIndex(x => x.Commit).IsUnique(); var user = modelBuilder.Entity(); user.HasIndex(x => x.CanonicalName).IsUnique(); user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict); - modelBuilder.Entity().HasIndex(x => new { x.UserId, x.Instance }).IsUnique(); + modelBuilder.Entity().HasIndex(x => new { x.UserId, x.InstanceId }).IsUnique(); var chatChannel = modelBuilder.Entity(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique(); diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index a9d82058d2..290417b1fe 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// /// Represents an in the database /// - public sealed class Instance : Api.Models.Instance + public sealed class Instance : Api.Models.Instance, IApiConvertable { /// /// The for the @@ -51,5 +52,16 @@ namespace Tgstation.Server.Host.Models /// The in the /// public List Jobs { get; set; } + + /// + public Api.Models.Instance ToApi() => new Api.Models.Instance + { + AutoUpdateInterval = AutoUpdateInterval, + ConfigurationAllowed = ConfigurationAllowed, + Id = Id, + Name = Name, + Path = Path, + Online = Online + }; } } diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 37fc313e04..43d9fae655 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class InstanceUser : Api.Models.InstanceUser + public sealed class InstanceUser : Api.Models.InstanceUser, IApiConvertable { /// /// The row Id @@ -24,5 +25,18 @@ namespace Tgstation.Server.Host.Models ConfigurationRights != Api.Rights.ConfigurationRights.None || DreamDaemonRights != Api.Rights.DreamDaemonRights.None || DreamMakerRights != Api.Rights.DreamMakerRights.None; + + /// + public Api.Models.InstanceUser ToApi() => new Api.Models.InstanceUser + { + ByondRights = ByondRights, + ChatSettingsRights = ChatSettingsRights, + ConfigurationRights = ConfigurationRights, + DreamDaemonRights = DreamDaemonRights, + DreamMakerRights = DreamMakerRights, + InstanceId = InstanceId, + RepositoryRights = RepositoryRights, + UserId = UserId + }; } } From f0cd3df98a16ea61ce9d200734ddff69422005a2 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 12:04:26 -0400 Subject: [PATCH 265/397] Various fixes --- .../Controllers/ChatController.cs | 7 ++---- .../Controllers/DreamDaemonController.cs | 5 +++- .../Controllers/DreamMakerController.cs | 11 +++++++-- .../Controllers/InstanceUserController.cs | 10 ++++---- .../Controllers/JobController.cs | 4 ++-- .../Controllers/ModelController.cs | 8 +++---- src/Tgstation.Server.Host/Core/JobManager.cs | 23 +++++++++++-------- 7 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 20afd6691e..6cda73d094 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -121,13 +121,10 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(ChatSettingsRights.Delete)] - public override async Task Delete([FromBody] Api.Models.ChatSettings model, CancellationToken cancellationToken) + public override async Task Delete(long id, CancellationToken cancellationToken) { - if (model == null) - throw new ArgumentNullException(nameof(model)); - var instance = instanceManager.GetInstance(Instance); - await Task.WhenAll(instance.Chat.DeleteConnection(model.Id, cancellationToken), DatabaseContext.ChatSettings.Where(x => x.Id == model.Id).DeleteAsync(cancellationToken)).ConfigureAwait(false); + await Task.WhenAll(instance.Chat.DeleteConnection(id, cancellationToken), DatabaseContext.ChatSettings.Where(x => x.Id == id).DeleteAsync(cancellationToken)).ConfigureAwait(false); return Ok(); } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index c1a4a6b673..b5574d49c7 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -49,6 +49,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(DreamDaemonRights.Start)] public override async Task Create([FromBody] DreamDaemon model, CancellationToken cancellationToken) { + //alias for launching DD var instance = instanceManager.GetInstance(Instance); if (instance.Watchdog.Running) @@ -113,8 +114,9 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamDaemonRights.Shutdown)] - public override async Task Delete([FromBody] DreamDaemon model, CancellationToken cancellationToken) + public override async Task Delete(long id, CancellationToken cancellationToken) { + //alias for stopping DD var instance = instanceManager.GetInstance(Instance); if (!instance.Watchdog.Running) @@ -128,6 +130,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(DreamDaemonRights.SetAutoStart | DreamDaemonRights.SetPorts | DreamDaemonRights.SetSecurity | DreamDaemonRights.SetWebClient | DreamDaemonRights.SoftRestart | DreamDaemonRights.SoftShutdown | DreamDaemonRights.Start | DreamDaemonRights.SetStartupTimeout)] public override async Task Update([FromBody] DreamDaemon model, CancellationToken cancellationToken) { + //alias for changing DD settings var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstAsync(cancellationToken).ConfigureAwait(false); var userRights = (DreamDaemonRights)AuthenticationContext.GetRight(RightsType.DreamDaemon); diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index b936a07f87..3437886044 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -75,13 +75,20 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.CancelCompile)] - public override async Task Delete([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) + public override async Task Delete(long id, CancellationToken cancellationToken) { //alias for cancelling the latest job var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Select(x => new Job { Id = x.Job.Id, StoppedAt = x.Job.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); if (job.StoppedAt != null) return StatusCode((int)HttpStatusCode.Gone); - await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); + try + { + await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); + } + catch (InvalidOperationException) //job already stopped + { + return StatusCode((int)HttpStatusCode.Gone); + } return Ok(); } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs index abe94e092c..9ddc1d43cc 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -140,7 +140,7 @@ namespace Tgstation.Server.Host.Controllers public override async Task GetId(long id, CancellationToken cancellationToken) { //this functions as userId - var user = await DatabaseContext.Instances.Where(x => x.Id == id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var user = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (user == default) return NotFound(); return Json(user.ToApi()); @@ -148,13 +148,11 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(AdministrationRights.EditUsers)] - public override async Task Delete([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken) + public override async Task Delete(long id, CancellationToken cancellationToken) { - var test = StandardModelChecks(model); - if (test != null) - return test; + //id is actually UserId - await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == model.UserId).DeleteAsync(cancellationToken).ConfigureAwait(false); + await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).DeleteAsync(cancellationToken).ConfigureAwait(false); return Ok(); } } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index cefec12234..8219125d29 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -53,10 +53,10 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize] - public override async Task Delete([FromBody] Api.Models.Job model, CancellationToken cancellationToken) + public override async Task Delete(long id, CancellationToken cancellationToken) { //don't care if an instance post or not at this point - var job = await DatabaseContext.Jobs.Where(x => x.Id == model.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var job = await DatabaseContext.Jobs.Where(x => x.Id == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (job == default(Job)) return NotFound(); diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index aec3cf3769..54ea395d7e 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -46,8 +46,8 @@ namespace Tgstation.Server.Host.Controllers /// /// Attempt to get a specific a /// - /// The for the operation /// The ID of the model to get + /// The for the operation /// A resulting in the of the operation [HttpGet("/{0}")] public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); @@ -62,13 +62,13 @@ namespace Tgstation.Server.Host.Controllers public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// - /// Attempt to delete a + /// Attempt to delete a model with a particular /// - /// The being deleted + /// The ID of the model to delete /// The for the operation /// A resulting in the of the operation [HttpDelete] - public virtual Task Delete([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); + public virtual Task Delete(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// /// Attempt to list entries of the diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 03f12ef437..2f0dcc2a94 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -149,16 +149,19 @@ namespace Tgstation.Server.Host.Core /// public async Task CancelJob(Job job, User user, CancellationToken cancellationToken) { - if (user != null) - using (var scope = serviceProvider.CreateScope()) - { - var databaseContext = scope.ServiceProvider.GetRequiredService(); - job = new Job { Id = job.Id }; - databaseContext.Jobs.Attach(job); - job.CancelledBy = user; - await databaseContext.Save(cancellationToken).ConfigureAwait(false); - } - CheckGetJob(job).Cancel(); + if (job == null) + throw new ArgumentNullException(nameof(job)); + if (user == null) + throw new ArgumentNullException(nameof(user)); + CheckGetJob(job).Cancel(); //this will ensure the db update is only done once + using (var scope = serviceProvider.CreateScope()) + { + var databaseContext = scope.ServiceProvider.GetRequiredService(); + job = new Job { Id = job.Id }; + databaseContext.Jobs.Attach(job); + job.CancelledBy = user; + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } } } } From 669c72a05f561de2e8a47c72dc879868855220ff Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 16:30:47 -0400 Subject: [PATCH 266/397] Windows identity/impersonation stuff --- src/Tgstation.Server.Host.Service/Program.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 2 +- src/Tgstation.Server.Host/NativeMethods.cs | 17 +++++++ ...ctory.cs => PosixSystemIdentityFactory.cs} | 6 ++- .../Security/WindowsSystemIdentity.cs | 47 +++++++++++++++++ .../Security/WindowsSystemIdentityFactory.cs | 51 +++++++++++++++++++ .../Tgstation.Server.Host.csproj | 1 + 7 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 src/Tgstation.Server.Host/NativeMethods.cs rename src/Tgstation.Server.Host/Security/{SystemIdentityFactory.cs => PosixSystemIdentityFactory.cs} (79%) create mode 100644 src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs create mode 100644 src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index 742b7e7ac6..96932140c2 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -75,7 +75,7 @@ namespace Tgstation.Server.Host.Service using (var processInstaller = new ServiceProcessInstaller()) using (var installer = new ServiceInstaller()) { - processInstaller.Account = ServiceAccount.NetworkService; + processInstaller.Account = ServiceAccount.LocalSystem; installer.Context = new InstallContext("tgs-4-install.log", new string[] { String.Format(CultureInfo.InvariantCulture, "/assemblypath={0}", Assembly.GetEntryAssembly().Location) }); installer.Description = "/tg/station 13 server v4 running as a windows service"; diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 351cb649ec..5b9648bd91 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -146,7 +146,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton, PasswordHasher>(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs new file mode 100644 index 0000000000..8a4038dc53 --- /dev/null +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host +{ + /// + /// Native methods used by the code + /// + static class NativeMethods + { + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx + /// + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); + } +} diff --git a/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs similarity index 79% rename from src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs rename to src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs index 4f384ff4b4..659fe36260 100644 --- a/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs @@ -5,8 +5,10 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { - /// - sealed class SystemIdentityFactory : ISystemIdentityFactory + /// + /// for posix systems + /// + sealed class PosixSystemIdentityFactory : ISystemIdentityFactory { /// public Task CreateSystemIdentity(User user, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs new file mode 100644 index 0000000000..626d699ac7 --- /dev/null +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs @@ -0,0 +1,47 @@ +using System; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Security +{ + /// + /// for windows systems + /// + sealed class WindowsSystemIdentity : ISystemIdentity + { + /// + /// The for the + /// + readonly WindowsIdentity identity; + + /// + /// Construct a + /// + /// The value of + public WindowsSystemIdentity(WindowsIdentity identity) + { + this.identity = identity ?? throw new ArgumentNullException(nameof(identity)); + } + + /// + public void Dispose() => identity.Dispose(); + + /// + public string Uid => identity.User.ToString(); + + /// + public string Username => identity.Name; + + /// + public ISystemIdentity Clone() => new WindowsSystemIdentity((WindowsIdentity)identity.Clone()); + + /// + public Task RunImpersonated(Action action, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + WindowsIdentity.RunImpersonated(identity.AccessToken, action); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs new file mode 100644 index 0000000000..286d2daaec --- /dev/null +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs @@ -0,0 +1,51 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.ComponentModel; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Security +{ + /// + /// for windows systems. Uses long running tasks due to potential networked domains + /// + sealed class WindowsSystemIdentityFactory : ISystemIdentityFactory + { + /// + public Task CreateSystemIdentity(User user, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + + if (user.SystemIdentifier == null) + throw new InvalidOperationException("User's SystemIdentifier must not be null!"); + + //System identity at this point will always be in the form DOMAIN\\USER or USER + var splits = user.SystemIdentifier.Split('\\'); + string identity; + if (splits.Length > 1) + identity = String.Format(CultureInfo.InvariantCulture, "{0}@{1}", splits[0], splits[1]); + else + identity = user.SystemIdentifier; + + return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(identity)); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + var splits = username.Split('\\'); + + var res = NativeMethods.LogonUser(splits.Length > 1 ? splits[1] : splits[0], splits.Length > 1 ? splits[0] : null, password, 3 /*LOGON32_LOGON_NETWORK*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out var token); + if (!res) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + using (var handle = new SafeAccessTokenHandle(token)) //checked internally, windows identity always duplicates the handle when constructed with a userToken + return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); //https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271 + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } +} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index aa1f136bcd..a396bd0c66 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -48,6 +48,7 @@ + From ca77f1fc449c454722e743336d009c82d3931fba Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 16:31:54 -0400 Subject: [PATCH 267/397] Use the right ISystemIdentityFactory for the system --- src/Tgstation.Server.Host/Core/Application.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 5b9648bd91..78587d13e1 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -14,6 +14,7 @@ using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Watchdog; @@ -144,10 +145,14 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); - services.AddSingleton, PasswordHasher>(); + services.AddSingleton, PasswordHasher>(); services.AddSingleton(); - services.AddSingleton(); - + + if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + services.AddSingleton(); + else + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(new ByondTopicSender From 0ce92f99575b76d5c9c903549262dd9c8ae382a9 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 16:35:40 -0400 Subject: [PATCH 268/397] Properly handle the lack of implementation for posix system identities --- src/Tgstation.Server.Host/Controllers/UserController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 50be28d1dd..59638e2026 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -86,6 +86,10 @@ namespace Tgstation.Server.Host.Controllers dbUser.SystemIdentifier = sysIdentity.Uid; } } + catch (NotImplementedException) + { + return StatusCode((int)HttpStatusCode.NotImplemented); + } catch(Exception e) { logger.LogInformation("System identifier user creation failure for {0}. Exception: {1}", model.SystemIdentifier, e); From fa20cf6ad933013988aa0921a9a2d9496f24b9cd Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 17:03:11 -0400 Subject: [PATCH 269/397] Fix IRC not listening --- .../Components/Chat/Providers/IrcProvider.cs | 138 ++++++++++-------- 1 file changed, 76 insertions(+), 62 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 7457d6f8d2..47be347344 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -71,6 +71,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// ulong channelIdCounter; + /// + /// The used for + /// + Task listenTask; + /// /// Construct an /// @@ -200,85 +205,94 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public override Task Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - try - { - client.Connect(address, port); - - cancellationToken.ThrowIfCancellationRequested(); - - if (passwordType == IrcPasswordType.Server) - client.Login(nickname, nickname, 0, nickname, password); - else + lock (this) + try { - if (passwordType == IrcPasswordType.Sasl) + client.Connect(address, port); + + cancellationToken.ThrowIfCancellationRequested(); + + if (passwordType == IrcPasswordType.Server) + client.Login(nickname, nickname, 0, nickname, password); + else { - client.WriteLine("CAP REQ :sasl", Priority.Critical); //needs to be put in the buffer before anything else - cancellationToken.ThrowIfCancellationRequested(); + if (passwordType == IrcPasswordType.Sasl) + { + 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); } - client.Login(nickname, nickname, 0, nickname); - } - if (passwordType == IrcPasswordType.NickServ) - { - cancellationToken.ThrowIfCancellationRequested(); - client.SendMessage(SendType.Message, "NickServ", String.Format(CultureInfo.InvariantCulture, "IDENTIFY {0}", password)); - } - else if (passwordType == IrcPasswordType.Sasl) - { - //wait for the sasl ack or timeout - var recievedAck = false; - var recievedPlus = false; - client.OnReadLine += (sender, e) => + if (passwordType == IrcPasswordType.NickServ) { - if (e.Line.Contains("ACK :sasl")) - recievedAck = true; - else if (e.Line.Contains("AUTHENTICATE +")) - recievedPlus = true; - }; + cancellationToken.ThrowIfCancellationRequested(); + client.SendMessage(SendType.Message, "NickServ", String.Format(CultureInfo.InvariantCulture, "IDENTIFY {0}", password)); + } + else if (passwordType == IrcPasswordType.Sasl) + { + //wait for the sasl ack or timeout + var recievedAck = false; + var recievedPlus = false; + client.OnReadLine += (sender, e) => + { + if (e.Line.Contains("ACK :sasl")) + recievedAck = true; + else if (e.Line.Contains("AUTHENTICATE +")) + recievedPlus = true; + }; - var startTime = DateTimeOffset.Now; - var endTime = DateTimeOffset.Now.AddSeconds(TimeoutSeconds); - cancellationToken.ThrowIfCancellationRequested(); - for(; !recievedAck && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult()) - client.Listen(false); - - client.WriteLine("AUTHENTICATE PLAIN", Priority.Critical); - cancellationToken.ThrowIfCancellationRequested(); + var startTime = DateTimeOffset.Now; + var endTime = DateTimeOffset.Now.AddSeconds(TimeoutSeconds); + cancellationToken.ThrowIfCancellationRequested(); + for (; !recievedAck && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult()) + client.Listen(false); - for (; !recievedPlus && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult()) - client.Listen(false); + client.WriteLine("AUTHENTICATE PLAIN", Priority.Critical); + cancellationToken.ThrowIfCancellationRequested(); - //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); - var chars = authLine.ToCharArray(); - client.WriteLine(authLine, Priority.Critical); + for (; !recievedPlus && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult()) + client.Listen(false); - cancellationToken.ThrowIfCancellationRequested(); - client.WriteLine("CAP END", Priority.Critical); + //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); + var chars = authLine.ToCharArray(); + client.WriteLine(authLine, Priority.Critical); + + cancellationToken.ThrowIfCancellationRequested(); + client.WriteLine("CAP END", Priority.Critical); + } + + listenTask = Task.Factory.StartNew(client.Listen(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } + catch (Exception e) + { + logger.LogWarning("Unable to connect to IRC: {0}", e); } - } - catch (Exception e) - { - logger.LogWarning("Unable to connect to IRC: {0}", e); - } return true; }, cancellationToken,TaskCreationOptions.LongRunning, TaskScheduler.Current); /// - public override Task Disconnect(CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public override async Task Disconnect(CancellationToken cancellationToken) { - try + if (!Connected) + return; + await Task.Factory.StartNew(() => { - client.RfcQuit(); - } - catch (Exception e) - { - logger.LogWarning("Error quitting IRC: {0}", e); - } + try + { + client.RfcQuit(); + } + catch (Exception e) + { + logger.LogWarning("Error quitting IRC: {0}", e); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); Dispose(); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + await listenTask.ConfigureAwait(false); + } /// public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() => From 2b3346af52e3409564e60233e6d2257bc0ca3c07 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 17:03:27 -0400 Subject: [PATCH 270/397] Fix compile error --- .../Components/Chat/Providers/IrcProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 47be347344..01e1a6964f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -265,7 +265,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers client.WriteLine("CAP END", Priority.Critical); } - listenTask = Task.Factory.StartNew(client.Listen(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + listenTask = Task.Factory.StartNew(() => client.Listen(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } catch (Exception e) { From 7cf4e870b2bced7af649d4e6f814306dfb3fdc4d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 17:28:22 -0400 Subject: [PATCH 271/397] Configuration cleanup --- ...{Configuration.cs => ConfigurationFile.cs} | 4 +-- .../Models/ConfigurationType.cs | 21 +++++++++++++ src/Tgstation.Server.Api/Models/Instance.cs | 9 +++--- .../Internal/ConfigurationFileMetadata.cs | 6 ++-- .../Rights/ConfigurationRights.cs | 2 +- .../Rights/InstanceUserRights.cs | 8 +++++ src/Tgstation.Server.Api/RouteHelper.cs | 24 +++++++-------- .../Components/IConfigurationClient.cs | 30 +++++++++---------- .../Components/Chat/Providers/IrcProvider.cs | 2 -- .../Components/IConfiguration.cs | 4 +-- .../Components/InstanceManager.cs | 6 ++-- .../Controllers/InstanceController.cs | 2 +- src/Tgstation.Server.Host/Models/Instance.cs | 2 +- 13 files changed, 74 insertions(+), 46 deletions(-) rename src/Tgstation.Server.Api/Models/{Configuration.cs => ConfigurationFile.cs} (62%) create mode 100644 src/Tgstation.Server.Api/Models/ConfigurationType.cs diff --git a/src/Tgstation.Server.Api/Models/Configuration.cs b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs similarity index 62% rename from src/Tgstation.Server.Api/Models/Configuration.cs rename to src/Tgstation.Server.Api/Models/ConfigurationFile.cs index 26f99815c9..3b3ca37e59 100644 --- a/src/Tgstation.Server.Api/Models/Configuration.cs +++ b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs @@ -6,11 +6,11 @@ namespace Tgstation.Server.Api.Models /// Represents a static game file. Create and delete actions uncerimonuously overwrite/delete files /// #pragma warning disable CA1724 // System.Configuration name conflict - public sealed class Configuration : ConfigurationFileMetadata + public sealed class ConfigurationFile : ConfigurationFileMetadata #pragma warning restore CA1724 // System.Configuration name conflict { /// - /// The content of the file. Will be if is or during listing operations + /// The content of the . Will be if is or during listing operations /// public byte[] Content { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/ConfigurationType.cs b/src/Tgstation.Server.Api/Models/ConfigurationType.cs new file mode 100644 index 0000000000..d322e79a75 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/ConfigurationType.cs @@ -0,0 +1,21 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// The type of configuration allowed on an + /// + public enum ConfigurationType + { + /// + /// Configuration editing is not allowed + /// + Disallowed, + /// + /// Configuration editing is allowed by all users on all files + /// + HostWrite, + /// + /// Co + /// + SystemIdentityWrite + } +} diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 74910fdf68..66e5238161 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -33,13 +33,14 @@ namespace Tgstation.Server.Api.Models /// If the is online /// [Permissions(WriteRight = InstanceManagerRights.SetOnline)] - public bool Online { get; set; } + public bool? Online { get; set; } /// - /// If can be used on the + /// If can be used on the /// [Permissions(WriteRight = InstanceManagerRights.SetConfiguration)] - public bool ConfigurationAllowed { get; set; } + [Required] + public ConfigurationType? ConfigurationType { get; set; } /// /// The time interval in minutes the repository is automatically pulled and compiles @@ -54,7 +55,7 @@ namespace Tgstation.Server.Api.Models Name = Name, Path = Path, Online = Online, - ConfigurationAllowed = ConfigurationAllowed + ConfigurationType = ConfigurationType }; } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs b/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs index e402872f3d..ea8f30c484 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs @@ -3,18 +3,18 @@ namespace Tgstation.Server.Api.Models.Internal { /// - /// Metadata about a file + /// Metadata about a file /// [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] public class ConfigurationFileMetadata { /// - /// The path to the file + /// The path to the file /// public string Path { get; set; } /// - /// If read access to the file was denied + /// If read access to the file was denied /// [Permissions(DenyWrite = true)] public bool ReadDenied { get; set; } diff --git a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs index 1246644afe..3008353ffa 100644 --- a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs +++ b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs @@ -3,7 +3,7 @@ namespace Tgstation.Server.Api.Rights { /// - /// Rights for + /// Rights for /// [Flags] public enum ConfigurationRights diff --git a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs index 26ac4d26ab..7c4e7586f8 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs @@ -20,5 +20,13 @@ namespace Tgstation.Server.Api.Rights /// Allow write access to for the /// WriteUsers = 2, + /// + /// User can read configuration files if the instance allows it + /// + ReadConfiguration = 4, + /// + /// Users can write configuration files if the instance allows it + /// + EditConfiguration = 8, } } diff --git a/src/Tgstation.Server.Api/RouteHelper.cs b/src/Tgstation.Server.Api/RouteHelper.cs index 82592c0654..cfe67e00cd 100644 --- a/src/Tgstation.Server.Api/RouteHelper.cs +++ b/src/Tgstation.Server.Api/RouteHelper.cs @@ -114,18 +114,18 @@ namespace Tgstation.Server.Api public static Route ServerVersion() => new Route { Path = "/", Method = HttpMethod.Get }; /// - /// Get the to read a file + /// Get the to read a file /// - /// The the file resides in - /// The path to the file in the directory + /// The the file resides in + /// The path to the file in the directory /// A to the read action public static Route ReadFile(Instance instance, string path) => new Route { Path = String.Concat("/Configuration/", instance?.Id ?? throw new ArgumentNullException(nameof(instance)), '/', path?.TrimStart('/') ?? throw new ArgumentNullException(nameof(path))), Method = HttpMethod.Get }; /// - /// Get the to list files for a + /// Get the to list files for a /// - /// The the file resides in - /// The directory to list + /// The the file resides in + /// The directory to list /// A to the read action public static Route ListFiles(Instance instance, string directory) { @@ -135,10 +135,10 @@ namespace Tgstation.Server.Api } /// - /// Get the to create a file + /// Get the to create a file /// - /// The the file resides in - /// The path to the file in the directory + /// The the file resides in + /// The path to the file in the directory /// A to the create action public static Route CreateFile(Instance instance, string path) { @@ -148,10 +148,10 @@ namespace Tgstation.Server.Api } /// - /// Get the to delete a file + /// Get the to delete a file /// - /// The the file resides in - /// The path to the file in the directory + /// The the file resides in + /// The path to the file in the directory /// A to the delete action public static Route DeleteFile(Instance instance, string path) { diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs index 7f7d05a477..af84c1ca73 100644 --- a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs @@ -8,7 +8,7 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Client.Components { /// - /// For managing files + /// For managing files /// public interface IConfigurationClient : IRightsClient { @@ -17,39 +17,39 @@ namespace Tgstation.Server.Client.Components /// /// The path to the directory to list files in /// The for the operation - /// A of s in the - Task> List(string directory, CancellationToken cancellationToken); + /// A of s in the + Task> List(string directory, CancellationToken cancellationToken); /// - /// Read a file + /// Read a file /// - /// The file to read + /// The file to read /// The for the operation /// A representing the running operation - Task Read(Configuration file, CancellationToken cancellationToken); + Task Read(ConfigurationFile file, CancellationToken cancellationToken); /// - /// Overwrite a file with integrity checks + /// Overwrite a file with integrity checks /// - /// The file to write + /// The file to write /// The for the operation /// A representing the running operation - Task Write(Configuration file, CancellationToken cancellationToken); + Task Write(ConfigurationFile file, CancellationToken cancellationToken); /// - /// Create/overwrite a file + /// Create/overwrite a file /// - /// The file to write + /// The file to write /// The for the operation /// A representing the running operation - Task Create(Configuration file, CancellationToken cancellationToken); + Task Create(ConfigurationFile file, CancellationToken cancellationToken); /// - /// Delete a file + /// Delete a file /// - /// The file to delete + /// The file to delete /// The for the operation /// A representing the running operation - Task Delete(Configuration file, CancellationToken cancellationToken); + Task Delete(ConfigurationFile file, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 01e1a6964f..44966939e3 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -314,8 +314,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers foreach (var I in hs) client.RfcJoin(I); - - return (IReadOnlyList)channels.Select(x => { ulong id = channelIdCounter; if (!channelIdMap.Any(y => diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index da32288f4d..4d0e88e7a9 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -42,8 +42,8 @@ namespace Tgstation.Server.Host.Components /// The relative path in the Configuration directory /// The for the operation. If , the operation will be performed as the user of the /// The for the operation - /// A resulting in the of the file - Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// A resulting in the of the file + Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// /// Writes to a given diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 24dd107c18..1d49f58fa6 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -74,7 +74,7 @@ namespace Tgstation.Server.Host.Components { if (newPath == null) throw new ArgumentNullException(nameof(newPath)); - if (instance.Online) + if (instance.Online.Value) await OfflineInstance(instance, cancellationToken).ConfigureAwait(false); Task instanceOnlineTask = null; try @@ -87,7 +87,7 @@ namespace Tgstation.Server.Host.Components } finally { - if (instance.Online) + if (instance.Online.Value) if (instanceOnlineTask == null) await OnlineInstance(instance, default).ConfigureAwait(false); else @@ -129,7 +129,7 @@ namespace Tgstation.Server.Host.Components public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext => { await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); - var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); + var dbInstances = databaseContext.Instances.Where(x => x.Online.Value).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); var tasks = new List(); await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index fb18f46897..95e59d61f8 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -59,7 +59,7 @@ namespace Tgstation.Server.Host.Controllers var newInstance = new Models.Instance { ChatSettings = new ChatSettings(), - ConfigurationAllowed = model.ConfigurationAllowed, + ConfigurationType = model.ConfigurationType, DreamDaemonSettings = new DreamDaemonSettings(), DreamMakerSettings = new DreamMakerSettings(), Name = model.Name, diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 290417b1fe..4b6b57762c 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Models public Api.Models.Instance ToApi() => new Api.Models.Instance { AutoUpdateInterval = AutoUpdateInterval, - ConfigurationAllowed = ConfigurationAllowed, + ConfigurationType = ConfigurationType, Id = Id, Name = Name, Path = Path, From cee2e76bdf9e7dcbc21b04cbbd109eadce7a401f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 18:02:41 -0400 Subject: [PATCH 272/397] ConfigurationController --- .../Models/ConfigurationFile.cs | 34 +++++- .../Internal/ConfigurationFileMetadata.cs | 33 ----- .../Rights/InstanceManagerRights.cs | 2 +- .../Rights/InstanceUserRights.cs | 10 +- .../Components/IConfiguration.cs | 14 +-- .../Controllers/ConfigurationController.cs | 113 ++++++++++++++++++ 6 files changed, 151 insertions(+), 55 deletions(-) delete mode 100644 src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs create mode 100644 src/Tgstation.Server.Host/Controllers/ConfigurationController.cs diff --git a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs index 3b3ca37e59..a7e7aad737 100644 --- a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs +++ b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs @@ -1,16 +1,40 @@ using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// - /// Represents a static game file. Create and delete actions uncerimonuously overwrite/delete files + /// Represents a game configuration file. Create and delete actions uncerimonuously overwrite/delete files /// -#pragma warning disable CA1724 // System.Configuration name conflict - public sealed class ConfigurationFile : ConfigurationFileMetadata -#pragma warning restore CA1724 // System.Configuration name conflict + [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] + public sealed class ConfigurationFile { /// - /// The content of the . Will be if is or during listing operations + /// The path to the file + /// + [Permissions(DenyWrite = true)] + public string Path { get; set; } + + /// + /// If read access to the file was denied + /// + [Permissions(DenyWrite = true)] + public bool? ReadDenied { get; set; } + + /// + /// If represents a directory. Will only be if is + /// + [Permissions(DenyWrite = true)] + public bool? IsDirectory { get; set; } + + /// + /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 + /// + [Permissions(DenyWrite = true)] + public string LastReadHash { get; set; } + + /// + /// The content of the . Will be if is or during listing operations /// public byte[] Content { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs b/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs deleted file mode 100644 index ea8f30c484..0000000000 --- a/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Tgstation.Server.Api.Rights; - -namespace Tgstation.Server.Api.Models.Internal -{ - /// - /// Metadata about a file - /// - [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] - public class ConfigurationFileMetadata - { - /// - /// The path to the file - /// - public string Path { get; set; } - - /// - /// If read access to the file was denied - /// - [Permissions(DenyWrite = true)] - public bool ReadDenied { get; set; } - - /// - /// If represents a directory. Will only be if is - /// - [Permissions(DenyWrite = true)] - public bool IsDirectory { get; set; } - - /// - /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 - /// - public string LastReadHash { get; set; } - } -} diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index fbec63f9d5..7d0a3c4ace 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -41,7 +41,7 @@ namespace Tgstation.Server.Api.Rights /// List = 64, /// - /// User can change + /// User can change /// SetConfiguration = 128, /// diff --git a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs index 7c4e7586f8..25b85acfbd 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs @@ -19,14 +19,6 @@ namespace Tgstation.Server.Api.Rights /// /// Allow write access to for the /// - WriteUsers = 2, - /// - /// User can read configuration files if the instance allows it - /// - ReadConfiguration = 4, - /// - /// Users can write configuration files if the instance allows it - /// - EditConfiguration = 8, + WriteUsers = 2 } } diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index 4d0e88e7a9..7cf71657cd 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components @@ -28,13 +28,13 @@ namespace Tgstation.Server.Host.Components Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken); /// - /// Get for all items in a given + /// Get for all items in a given /// /// The relative path in the Configuration directory /// The for the operation. If , the operation will be performed as the user of the /// The for the operation - /// A resulting in the for the items in the directory - Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// A resulting in the s for the items in the directory. and will both be + Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// /// Reads a given @@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Components /// The for the operation. If , the operation will be performed as the user of the /// The for the operation /// A resulting in the of the file - Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// /// Writes to a given @@ -52,7 +52,7 @@ namespace Tgstation.Server.Host.Components /// The for the operation. If , the operation will be performed as the user of the /// The data to write. If , the file is deleted /// The for the operation. Usage may result in partial writes - /// A resulting in if the operation succeeded, if it failed due to permission errors - Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken); + /// A resulting in the value for + Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs new file mode 100644 index 0000000000..a587dddf54 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -0,0 +1,113 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// The for s + /// + [Route("/Configuration")] + public sealed class ConfigurationController : ModelController + { + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + /// The value of + public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + + /// + [TgsAuthorize(ConfigurationRights.Write)] + public override async Task Update([FromBody] ConfigurationFile model, CancellationToken cancellationToken) + { + if (Instance.ConfigurationType == ConfigurationType.Disallowed) + return Forbid(); + + var config = instanceManager.GetInstance(Instance).Configuration; + try + { + var originalFile = await config.Read(model.Path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + + if (model.LastReadHash != originalFile.LastReadHash) + return Conflict(); + + originalFile.LastReadHash = await config.Write(model.Path, AuthenticationContext.SystemIdentity, model.Content, cancellationToken).ConfigureAwait(false); + originalFile.Content = null; + + return Json(originalFile); + } + catch (InvalidOperationException) + { + return BadRequest(new { message = "Attempted to delete required folder!" }); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } + } + + /// + /// Get the contents of a file at a + /// + /// The path of the file to get + /// The for the operation + /// A resulting in the for the operation + [HttpGet("/File/{path}")] + [TgsAuthorize(ConfigurationRights.Read)] + public async Task File(string path, CancellationToken cancellationToken) + { + if (Instance.ConfigurationType == ConfigurationType.Disallowed) + return Forbid(); + + var result = await instanceManager.GetInstance(Instance).Configuration.Read(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + if (result == null || result.IsDirectory.Value) + return NotFound(); + + return Json(result); + } + + /// + /// Get the contents of a directory at a + /// + /// The path of the directory to get + /// The for the operation + /// A resulting in the for the operation + [HttpGet("/Directory/{path}")] + [TgsAuthorize(ConfigurationRights.List)] + public async Task Directory(string path, CancellationToken cancellationToken) + { + if (Instance.ConfigurationType == ConfigurationType.Disallowed) + return Forbid(); + + var result = await instanceManager.GetInstance(Instance).Configuration.ListDirectory(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + if (result == null) + return NotFound(); + + return Json(result); + } + } +} From 722d520b85de05de3aeec8100843274c5c526453 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 18:07:22 -0400 Subject: [PATCH 273/397] Skeleton Configuration component --- .../Components/Configuration.cs | 66 +++++++++++++++++++ .../Components/InstanceFactory.cs | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Host/Components/Configuration.cs diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs new file mode 100644 index 0000000000..caeab93c8f --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class Configuration : IConfiguration + { + /// + /// The for + /// + readonly IIOManager ioManager; + + /// + /// The for + /// + readonly ILogger logger; + + /// + /// Construct + /// + /// The value of + /// The value of + public Configuration(IIOManager ioManager, ILogger logger) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + public Task CopyDMFilesTo(string destination, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index efbefbe737..7c2fc53965 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -102,7 +102,7 @@ namespace Tgstation.Server.Host.Components var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); IByond byond = null; - IConfiguration configuration = null; + var configuration = new Configuration(configurationIoManager, loggerFactory.CreateLogger()); var chat = chatFactory.CreateChat(); var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata); From cf1bbb73f2572e4c9e6af37d64c9ec1bea2de38d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 18:43:03 -0400 Subject: [PATCH 274/397] Implement CopyDMFilesTo --- .../Components/Configuration.cs | 39 ++++++++++++++++++- .../Components/DreamMaker.cs | 2 +- .../Components/InstanceFactory.cs | 1 - 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index caeab93c8f..ab24c9173d 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -12,6 +13,13 @@ namespace Tgstation.Server.Host.Components /// sealed class Configuration : IConfiguration { + const string CodeModificationsSubdirectory = "CodeModifications"; + const string EventScriptsSubdirectory = "EventScripts"; + const string GameStaticFilesSubdirectory = "GameStaticFiles"; + + const string CodeModificationsHeadFile = "HeadInclude.dm"; + const string CodeModificationsTailFile = "TailInclude.dm"; + /// /// The for /// @@ -34,9 +42,36 @@ namespace Tgstation.Server.Host.Components } /// - public Task CopyDMFilesTo(string destination, CancellationToken cancellationToken) + public async Task CopyDMFilesTo(string destination, CancellationToken cancellationToken) { - throw new NotImplementedException(); + //just assume no other fs race conditions here + var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken); + var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken); + var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken); + + await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask).ConfigureAwait(false); + + if (!dmeExistsTask.Result && !headFileExistsTask.Result && !tailFileExistsTask.Result) + return null; + + var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, cancellationToken); + + if (dmeExistsTask.Result) + { + await copyTask.ConfigureAwait(false); + return new ServerSideModifications(null, null, true); + } + + if (!headFileExistsTask.Result && !tailFileExistsTask.Result) + { + await copyTask.ConfigureAwait(false); + return null; + } + + string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath); + + await copyTask.ConfigureAwait(false); + return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false); } /// diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 5d3caa81d1..a55b2c1016 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -212,7 +212,7 @@ namespace Tgstation.Server.Host.Components } else if (line.Contains("END_INCLUDE") && dmeModifications.TailIncludeLine != null) { - dmeLines.Insert(I - 1, dmeModifications.TailIncludeLine); + dmeLines.Insert(I, dmeModifications.TailIncludeLine); break; } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 7c2fc53965..09467dedda 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -93,7 +93,6 @@ namespace Tgstation.Server.Host.Components var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond"); var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); - var codeModificationsIoMananger = new ResolvingIOManager(instanceIoManager, "CodeModifications"); var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, metadata); var commandFactory = new CommandFactory(application); From eb925ea4cde66124bd15b3b2389dcc2d9a51564b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 19:30:52 -0400 Subject: [PATCH 275/397] Some stuff --- .../Components/Configuration.cs | 6 +++++- src/Tgstation.Server.Host/Components/DreamMaker.cs | 2 +- .../Components/IConfiguration.cs | 3 ++- src/Tgstation.Server.Host/Core/DefaultIOManager.cs | 13 +++++++++++++ src/Tgstation.Server.Host/Core/IIOManager.cs | 8 ++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index ab24c9173d..8f759f906b 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -42,7 +43,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task CopyDMFilesTo(string destination, CancellationToken cancellationToken) + public async Task CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken) { //just assume no other fs race conditions here var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken); @@ -77,6 +78,9 @@ namespace Tgstation.Server.Host.Components /// public Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) { + if (String.IsNullOrEmpty(configurationRelativePath)) + configurationRelativePath = "."; + throw new NotImplementedException(); } diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index a55b2c1016..4f403b2797 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -191,7 +191,7 @@ namespace Tgstation.Server.Host.Components var dmePath = ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmeExtension)); var dmeReadTask = ioManager.ReadAllBytes(dmePath, cancellationToken); - var dmeModificationsTask = configuration.CopyDMFilesTo(ioManager.ResolvePath(dirA), cancellationToken); + var dmeModificationsTask = configuration.CopyDMFilesTo(dmePath, ioManager.ResolvePath(dirA), cancellationToken); var dmeBytes = await dmeReadTask.ConfigureAwait(false); var dme = Encoding.UTF8.GetString(dmeBytes); diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index 7cf71657cd..1f743dd4b8 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -14,10 +14,11 @@ namespace Tgstation.Server.Host.Components /// /// Copies all files in the CodeModifications directory to /// + /// The .dme file being compiled /// Path to the destination folder /// The for the operation /// A resulting in the if any - Task CopyDMFilesTo(string destination, CancellationToken cancellationToken); + Task CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken); /// /// Symlinks all directories in the GameData directory to diff --git a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs index a5c3d59eb7..900acc5cc4 100644 --- a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs @@ -220,5 +220,18 @@ namespace Tgstation.Server.Host.Core } return (IReadOnlyList)results; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + path = ResolvePath(path); + var results = new List(); + foreach (var I in Directory.EnumerateFiles(path)) + { + cancellationToken.ThrowIfCancellationRequested(); + results.Add(I); + } + return (IReadOnlyList)results; + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Core/IIOManager.cs b/src/Tgstation.Server.Host/Core/IIOManager.cs index 69a207e692..1ded3a21c7 100644 --- a/src/Tgstation.Server.Host/Core/IIOManager.cs +++ b/src/Tgstation.Server.Host/Core/IIOManager.cs @@ -72,6 +72,14 @@ namespace Tgstation.Server.Host.Core /// A resulting in the directories in Task> GetDirectories(string path, CancellationToken cancellationToken); + /// + /// Returns file names in a given + /// + /// The path to search for files + /// The for the operation + /// A resulting in the files in + Task> GetFiles(string path, CancellationToken cancellationToken); + /// /// Writes some to a file at overwriting previous content /// From b94f6616205a3d5c9a91314391dc059dde289f9b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 11:33:37 -0400 Subject: [PATCH 276/397] Handle NotImplementedExceptions in configuration controller. May be a while before system identities actually work --- .../Controllers/ConfigurationController.cs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index a587dddf54..eb9ed1ea7b 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; +using System.Net; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -64,6 +65,10 @@ namespace Tgstation.Server.Host.Controllers { return BadRequest(new { message = "Attempted to delete required folder!" }); } + catch(NotImplementedException e) + { + return StatusCode((int)HttpStatusCode.NotImplemented, new { message = e.Message }); + } catch (UnauthorizedAccessException) { return Forbid(); @@ -82,12 +87,18 @@ namespace Tgstation.Server.Host.Controllers { if (Instance.ConfigurationType == ConfigurationType.Disallowed) return Forbid(); + try + { + var result = await instanceManager.GetInstance(Instance).Configuration.Read(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + if (result == null || result.IsDirectory.Value) + return NotFound(); - var result = await instanceManager.GetInstance(Instance).Configuration.Read(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); - if (result == null || result.IsDirectory.Value) - return NotFound(); - - return Json(result); + return Json(result); + } + catch (NotImplementedException e) + { + return StatusCode((int)HttpStatusCode.NotImplemented, new { message = e.Message }); + } } /// @@ -103,11 +114,18 @@ namespace Tgstation.Server.Host.Controllers if (Instance.ConfigurationType == ConfigurationType.Disallowed) return Forbid(); - var result = await instanceManager.GetInstance(Instance).Configuration.ListDirectory(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); - if (result == null) - return NotFound(); + try + { + var result = await instanceManager.GetInstance(Instance).Configuration.ListDirectory(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + if (result == null) + return NotFound(); - return Json(result); + return Json(result); + } + catch (NotImplementedException e) + { + return StatusCode((int)HttpStatusCode.NotImplemented, new { message = e.Message }); + } } } } From 1999bd33961140911384ab4e251bbd22b6e3e1fa Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 11:38:36 -0400 Subject: [PATCH 277/397] Cleanup of nuget packages --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index a396bd0c66..02f909a7b8 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -36,7 +36,7 @@ - + @@ -46,10 +46,10 @@ - + + - - + From 04d71065a9827aeb24884db4b61d6d177ce57b45 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:14:18 -0400 Subject: [PATCH 278/397] Adds ISynchronousIOManager --- .../Core/DefaultIOManager.cs | 13 ------ src/Tgstation.Server.Host/Core/IIOManager.cs | 8 ---- .../Core/ISynchronousIOManager.cs | 45 +++++++++++++++++++ 3 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs diff --git a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs index 900acc5cc4..a5c3d59eb7 100644 --- a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs @@ -220,18 +220,5 @@ namespace Tgstation.Server.Host.Core } return (IReadOnlyList)results; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); - - /// - public Task> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - path = ResolvePath(path); - var results = new List(); - foreach (var I in Directory.EnumerateFiles(path)) - { - cancellationToken.ThrowIfCancellationRequested(); - results.Add(I); - } - return (IReadOnlyList)results; - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Core/IIOManager.cs b/src/Tgstation.Server.Host/Core/IIOManager.cs index 1ded3a21c7..69a207e692 100644 --- a/src/Tgstation.Server.Host/Core/IIOManager.cs +++ b/src/Tgstation.Server.Host/Core/IIOManager.cs @@ -72,14 +72,6 @@ namespace Tgstation.Server.Host.Core /// A resulting in the directories in Task> GetDirectories(string path, CancellationToken cancellationToken); - /// - /// Returns file names in a given - /// - /// The path to search for files - /// The for the operation - /// A resulting in the files in - Task> GetFiles(string path, CancellationToken cancellationToken); - /// /// Writes some to a file at overwriting previous content /// diff --git a/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs new file mode 100644 index 0000000000..0620ff67d7 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Tgstation.Server.Host.Core +{ + /// + /// For accessing the disk in a synchronous manner + /// + interface ISynchronousIOManager + { + /// + /// Enumerate files in a given + /// + /// The path to look for files in + /// The for the operation + /// A of file names in + IEnumerable GetFiles(string path, CancellationToken cancellationToken); + + /// + /// Enumerate directories in a given + /// + /// The path to look for directories in + /// The for the operation + /// A of directory names in + IEnumerable GetDirectories(string path, CancellationToken cancellationToken); + + /// + /// Read the s of a file at a given + /// + /// The path of the file to read + /// The for the operation + /// A array representing the contents of the file at + byte[] ReadFile(string path, CancellationToken cancellationToken); + + /// + /// Write to a file at a given . + /// + /// The path to the file to write + /// The new contents of the file + /// If the file exists, this function only succeeds if this parameter matches the SHA-1 hash of the contents of the current file + /// The for the operation + /// on success, if the operation failed due to not matching the file's contents + bool WriteFileChecked(string path, byte[] data, string previousSha1, CancellationToken cancellationToken); + } +} From fc76fa28309a0651727b452934fe6cb8eeaf90a8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:15:28 -0400 Subject: [PATCH 279/397] Adds skeleton SynchronousIOManager implementation --- .../Core/SynchronousIOManager.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/Tgstation.Server.Host/Core/SynchronousIOManager.cs diff --git a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs new file mode 100644 index 0000000000..9dc1152890 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class SynchronousIOManager : ISynchronousIOManager + { + /// + public IEnumerable GetDirectories(string path, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public IEnumerable GetFiles(string path, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public byte[] ReadFile(string path, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public bool WriteFileChecked(string path, byte[] data, string previousSha1, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} From da44c9845944c4f789cbb3bdc6ec317e031f60c0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:16:03 -0400 Subject: [PATCH 280/397] Maps ISynchronousIOManager to SynchronousIOManager in services setup --- src/Tgstation.Server.Host/Core/Application.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 78587d13e1..8040fe1464 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -147,8 +147,9 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton, PasswordHasher>(); services.AddSingleton(); + services.AddSingleton(); - if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) services.AddSingleton(); else services.AddSingleton(); From 7732eef2a24dcd9e029b5fc9d582b61b777e21c7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:17:59 -0400 Subject: [PATCH 281/397] Implement SynchronousIOManager.GetDirectories --- src/Tgstation.Server.Host/Core/SynchronousIOManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs index 9dc1152890..f4064ce9f8 100644 --- a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs @@ -11,7 +11,12 @@ namespace Tgstation.Server.Host.Core /// public IEnumerable GetDirectories(string path, CancellationToken cancellationToken) { - throw new NotImplementedException(); + cancellationToken.ThrowIfCancellationRequested(); + foreach (var I in Directory.EnumerateDirectories(path)) + { + yield return I; + cancellationToken.ThrowIfCancellationRequested(); + } } /// From 2e866ce0fed74b9936aae44b51ad21fca7a9fe15 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:18:25 -0400 Subject: [PATCH 282/397] Implement SynchronousIOManager.GetFiles --- src/Tgstation.Server.Host/Core/SynchronousIOManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs index f4064ce9f8..04cb28f41a 100644 --- a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs @@ -22,7 +22,12 @@ namespace Tgstation.Server.Host.Core /// public IEnumerable GetFiles(string path, CancellationToken cancellationToken) { - throw new NotImplementedException(); + cancellationToken.ThrowIfCancellationRequested(); + foreach (var I in Directory.EnumerateFiles(path)) + { + yield return I; + cancellationToken.ThrowIfCancellationRequested(); + } } /// From fbdf4288b40e7fd924700c2c608562e954d46621 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:21:28 -0400 Subject: [PATCH 283/397] Implement SynchronousIOManager.ReadFile Removed cancellationToken parameter due to needless implementation complexity --- src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs | 3 +-- src/Tgstation.Server.Host/Core/SynchronousIOManager.cs | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs index 0620ff67d7..6faa6249ad 100644 --- a/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs @@ -28,9 +28,8 @@ namespace Tgstation.Server.Host.Core /// Read the s of a file at a given /// /// The path of the file to read - /// The for the operation /// A array representing the contents of the file at - byte[] ReadFile(string path, CancellationToken cancellationToken); + byte[] ReadFile(string path); /// /// Write to a file at a given . diff --git a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs index 04cb28f41a..ee281a8cdd 100644 --- a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs @@ -31,10 +31,7 @@ namespace Tgstation.Server.Host.Core } /// - public byte[] ReadFile(string path, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public byte[] ReadFile(string path) => File.ReadAllBytes(path); /// public bool WriteFileChecked(string path, byte[] data, string previousSha1, CancellationToken cancellationToken) From 6d4449050d23174b9973e5cff68de2177ee410fd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:33:02 -0400 Subject: [PATCH 284/397] Implement SynchronousIOManager.WriteFileChecked --- .../Core/SynchronousIOManager.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs index ee281a8cdd..dc56319f11 100644 --- a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Linq; +using System.Security.Cryptography; using System.Threading; namespace Tgstation.Server.Host.Core @@ -36,7 +39,37 @@ namespace Tgstation.Server.Host.Core /// public bool WriteFileChecked(string path, byte[] data, string previousSha1, CancellationToken cancellationToken) { - throw new NotImplementedException(); + cancellationToken.ThrowIfCancellationRequested(); + using (var file = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + { + using (var readMs = new MemoryStream()) + { + cancellationToken.ThrowIfCancellationRequested(); + file.CopyTo(readMs); + if (readMs.Length != 0 && previousSha1 == null) + return false; //no sha1? no write + //suppressed due to only using for consistency checks +#pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. + using (var sha1 = new SHA1Managed()) +#pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. + { + var sha1String = String.Join("", sha1.ComputeHash(readMs).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + if (sha1String != previousSha1) + return false; + } + } + + cancellationToken.ThrowIfCancellationRequested(); + file.Seek(0, SeekOrigin.Begin); + + cancellationToken.ThrowIfCancellationRequested(); + file.SetLength(data.Length); + + cancellationToken.ThrowIfCancellationRequested(); + file.Write(data, 0, data.Length); + + return true; + } } } } From 06ac519af59109be9213b7c334c34af32750291d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:35:32 -0400 Subject: [PATCH 285/397] Add ISynchronousIOManager as a dependency for the Configuration component --- .../Components/Configuration.cs | 9 ++++++++- .../Components/InstanceFactory.cs | 13 ++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index 8f759f906b..36e1934498 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -26,6 +26,11 @@ namespace Tgstation.Server.Host.Components /// readonly IIOManager ioManager; + /// + /// The for + /// + readonly ISynchronousIOManager synchronousIOManager; + /// /// The for /// @@ -35,10 +40,12 @@ namespace Tgstation.Server.Host.Components /// Construct /// /// The value of + /// The value of /// The value of - public Configuration(IIOManager ioManager, ILogger logger) + public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 09467dedda..3c9466e415 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -56,7 +56,12 @@ namespace Tgstation.Server.Host.Components /// The for the /// readonly ICommandFactory commandFactory; - + + /// + /// The for the + /// + readonly ISynchronousIOManager synchronousIOManager; + /// /// Construct an /// @@ -69,7 +74,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -80,6 +86,7 @@ namespace Tgstation.Server.Host.Components this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite )); this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); + this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); } /// @@ -101,7 +108,7 @@ namespace Tgstation.Server.Host.Components var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); IByond byond = null; - var configuration = new Configuration(configurationIoManager, loggerFactory.CreateLogger()); + var configuration = new Configuration(configurationIoManager, synchronousIOManager, loggerFactory.CreateLogger()); var chat = chatFactory.CreateChat(); var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata); From 8ae91abaf8942954988dec14a4de7eb56781978d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:41:02 -0400 Subject: [PATCH 286/397] Implement Components.Configuration.ListDirectory --- .../Components/Configuration.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index 36e1934498..1ab7412e7d 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -83,12 +83,37 @@ namespace Tgstation.Server.Host.Components } /// - public Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) + public async Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) { if (String.IsNullOrEmpty(configurationRelativePath)) configurationRelativePath = "."; - throw new NotImplementedException(); + var path = ioManager.ResolvePath(configurationRelativePath); + + List result = new List(); + + void ListImpl() + { + var enumerator = synchronousIOManager.GetDirectories(configurationRelativePath, cancellationToken); + result.AddRange(enumerator.Select(x => new ConfigurationFile + { + IsDirectory = true, + Path = ioManager.ConcatPath(path, x), + })); + enumerator = synchronousIOManager.GetFiles(configurationRelativePath, cancellationToken); + result.AddRange(enumerator.Select(x => new ConfigurationFile + { + IsDirectory = false, + Path = ioManager.ConcatPath(path, x), + })); + } + + if (systemIdentity == null) + ListImpl(); + else + await systemIdentity.RunImpersonated(ListImpl, cancellationToken).ConfigureAwait(false); + + return result; } /// From 5a3303301dc94e4dc8d07dccbb9d32a4894dd71d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 12:54:05 -0400 Subject: [PATCH 287/397] Implement more configuration stuff --- .../Models/ConfigurationFile.cs | 10 ++-- .../Components/Configuration.cs | 60 ++++++++++++++++--- .../Controllers/ConfigurationController.cs | 8 +-- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs index a7e7aad737..1c27df900c 100644 --- a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs +++ b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs @@ -16,25 +16,25 @@ namespace Tgstation.Server.Api.Models public string Path { get; set; } /// - /// If read access to the file was denied + /// If access to the file was denied for the operation /// [Permissions(DenyWrite = true)] - public bool? ReadDenied { get; set; } + public bool? AccessDenied { get; set; } /// - /// If represents a directory. Will only be if is + /// If represents a directory /// [Permissions(DenyWrite = true)] public bool? IsDirectory { get; set; } /// - /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 + /// The MD5 hash of the file when last read by the user. If this doesn't match during update actions, the write will be denied with /// [Permissions(DenyWrite = true)] public string LastReadHash { get; set; } /// - /// The content of the . Will be if is or during listing operations + /// The content of the . Will be if is or during listing and write operations /// public byte[] Content { get; set; } } diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index 1ab7412e7d..d6de13df5c 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -82,13 +84,17 @@ namespace Tgstation.Server.Host.Components return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false); } - /// - public async Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) + string ValidateConfigRelativePath(string configurationRelativePath) { if (String.IsNullOrEmpty(configurationRelativePath)) configurationRelativePath = "."; + return ioManager.ResolvePath(configurationRelativePath); + } - var path = ioManager.ResolvePath(configurationRelativePath); + /// + public async Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) + { + var path = ValidateConfigRelativePath(configurationRelativePath); List result = new List(); @@ -98,13 +104,13 @@ namespace Tgstation.Server.Host.Components result.AddRange(enumerator.Select(x => new ConfigurationFile { IsDirectory = true, - Path = ioManager.ConcatPath(path, x), + Path = ioManager.ConcatPath(configurationRelativePath, x), })); enumerator = synchronousIOManager.GetFiles(configurationRelativePath, cancellationToken); result.AddRange(enumerator.Select(x => new ConfigurationFile { IsDirectory = false, - Path = ioManager.ConcatPath(path, x), + Path = ioManager.ConcatPath(configurationRelativePath, x), })); } @@ -117,9 +123,49 @@ namespace Tgstation.Server.Host.Components } /// - public Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) + public async Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) { - throw new NotImplementedException(); + var path = ValidateConfigRelativePath(configurationRelativePath); + + ConfigurationFile result = null; + + void ReadImpl() + { + try + { + var content = synchronousIOManager.ReadFile(path); + string sha1String; +#pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. + using (var sha1 = new SHA1Managed()) +#pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. + sha1String = String.Join("", sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + result = new ConfigurationFile + { + Content = content, + IsDirectory = false, + LastReadHash = sha1String, + AccessDenied = false, + Path = configurationRelativePath + }; + } + catch (FileNotFoundException) { } + catch (DirectoryNotFoundException) { } + catch (UnauthorizedAccessException) + { + result = new ConfigurationFile + { + AccessDenied = true, + Path = configurationRelativePath + }; + } + } + + if (systemIdentity == null) + ReadImpl(); + else + await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false); + + return result; } /// diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index eb9ed1ea7b..bbdfd048b7 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -69,10 +69,6 @@ namespace Tgstation.Server.Host.Controllers { return StatusCode((int)HttpStatusCode.NotImplemented, new { message = e.Message }); } - catch (UnauthorizedAccessException) - { - return Forbid(); - } } /// @@ -126,6 +122,10 @@ namespace Tgstation.Server.Host.Controllers { return StatusCode((int)HttpStatusCode.NotImplemented, new { message = e.Message }); } + catch (UnauthorizedAccessException) + { + return Forbid(); + } } } } From d216471a6ab8c62fe95f5d3262a6cf86a2319a00 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 13:06:15 -0400 Subject: [PATCH 288/397] Finish config modification stuff --- .../Components/Configuration.cs | 46 ++++++++++++++++++- .../Components/IConfiguration.cs | 5 +- .../Controllers/ConfigurationController.cs | 25 ++++++---- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index d6de13df5c..ee163ece83 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -175,9 +175,51 @@ namespace Tgstation.Server.Host.Components } /// - public Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken) + public async Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, string previousHash, CancellationToken cancellationToken) { - throw new NotImplementedException(); + var path = ValidateConfigRelativePath(configurationRelativePath); + + ConfigurationFile result = null; + + void WriteImpl() + { + try + { + var success = synchronousIOManager.WriteFileChecked(path, data, previousHash, cancellationToken); + if (!success) + return; + string sha1String; +#pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. + using (var sha1 = new SHA1Managed()) +#pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. + sha1String = String.Join("", sha1.ComputeHash(data).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + result = new ConfigurationFile + { + Content = data, + IsDirectory = false, + LastReadHash = sha1String, + AccessDenied = false, + Path = configurationRelativePath + }; + } + catch (FileNotFoundException) { } + catch (DirectoryNotFoundException) { } + catch (UnauthorizedAccessException) + { + result = new ConfigurationFile + { + AccessDenied = true, + Path = configurationRelativePath + }; + } + } + + if (systemIdentity == null) + WriteImpl(); + else + await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false); + + return result; } } } diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index 1f743dd4b8..f1734e028d 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -52,8 +52,9 @@ namespace Tgstation.Server.Host.Components /// The relative path in the Configuration directory /// The for the operation. If , the operation will be performed as the user of the /// The data to write. If , the file is deleted + /// The hash any existing file must match in order for the write to succeed /// The for the operation. Usage may result in partial writes - /// A resulting in the value for - Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken); + /// A resulting in the updated + Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, string previousHash, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index bbdfd048b7..e6a3b4d81c 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -41,25 +41,29 @@ namespace Tgstation.Server.Host.Controllers this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); } + /// + /// If a should be returned from actions due to conflicts with one or both of the or the + /// + /// if a should be returned, otherwise + bool ForbidDueToModeConflicts() => Instance.ConfigurationType == ConfigurationType.Disallowed || (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null); + /// [TgsAuthorize(ConfigurationRights.Write)] public override async Task Update([FromBody] ConfigurationFile model, CancellationToken cancellationToken) { - if (Instance.ConfigurationType == ConfigurationType.Disallowed) + if (ForbidDueToModeConflicts()) return Forbid(); var config = instanceManager.GetInstance(Instance).Configuration; try { - var originalFile = await config.Read(model.Path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); - - if (model.LastReadHash != originalFile.LastReadHash) + var newFile = await config.Write(model.Path, AuthenticationContext.SystemIdentity, model.Content, model.LastReadHash, cancellationToken).ConfigureAwait(false); + if (newFile == null) return Conflict(); - originalFile.LastReadHash = await config.Write(model.Path, AuthenticationContext.SystemIdentity, model.Content, cancellationToken).ConfigureAwait(false); - originalFile.Content = null; + newFile.Content = null; - return Json(originalFile); + return Json(newFile); } catch (InvalidOperationException) { @@ -81,8 +85,9 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(ConfigurationRights.Read)] public async Task File(string path, CancellationToken cancellationToken) { - if (Instance.ConfigurationType == ConfigurationType.Disallowed) + if (ForbidDueToModeConflicts()) return Forbid(); + try { var result = await instanceManager.GetInstance(Instance).Configuration.Read(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); @@ -103,11 +108,11 @@ namespace Tgstation.Server.Host.Controllers /// The path of the directory to get /// The for the operation /// A resulting in the for the operation - [HttpGet("/Directory/{path}")] + [HttpGet("/List/{path}")] [TgsAuthorize(ConfigurationRights.List)] public async Task Directory(string path, CancellationToken cancellationToken) { - if (Instance.ConfigurationType == ConfigurationType.Disallowed) + if (ForbidDueToModeConflicts()) return Forbid(); try From c0e78c1a2a6e1dd01a09ae1c8b4c0aca27ab4229 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 13:22:28 -0400 Subject: [PATCH 289/397] More cleanup and move IO stuff to it's own namespace --- src/Tgstation.Server.Host/Components/Chat/Chat.cs | 2 +- .../Components/Chat/ChatFactory.cs | 2 +- .../Components/Chat/JsonTrackingContext.cs | 1 + src/Tgstation.Server.Host/Components/Configuration.cs | 2 +- src/Tgstation.Server.Host/Components/DmbFactory.cs | 1 + src/Tgstation.Server.Host/Components/DmbProvider.cs | 2 +- src/Tgstation.Server.Host/Components/DreamMaker.cs | 1 + .../Components/InstanceFactory.cs | 1 + src/Tgstation.Server.Host/Components/Repository.cs | 2 +- .../Components/RepositoryManager.cs | 2 +- .../Components/Watchdog/SessionControllerFactory.cs | 1 + src/Tgstation.Server.Host/Core/Application.cs | 1 + .../{Core => IO}/DefaultIOManager.cs | 10 +++++++++- src/Tgstation.Server.Host/{Core => IO}/IIOManager.cs | 2 +- .../{Core => IO}/ISynchronousIOManager.cs | 2 +- .../{Core => IO}/ResolvingIOManager.cs | 2 +- .../{Core => IO}/SynchronousIOManager.cs | 2 +- src/Tgstation.Server.Host/NativeMethods.cs | 6 ++++++ src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 3 ++- 19 files changed, 33 insertions(+), 12 deletions(-) rename src/Tgstation.Server.Host/{Core => IO}/DefaultIOManager.cs (96%) rename src/Tgstation.Server.Host/{Core => IO}/IIOManager.cs (99%) rename src/Tgstation.Server.Host/{Core => IO}/ISynchronousIOManager.cs (98%) rename src/Tgstation.Server.Host/{Core => IO}/ResolvingIOManager.cs (96%) rename src/Tgstation.Server.Host/{Core => IO}/SynchronousIOManager.cs (98%) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 7febaa1f3b..e878aedaa3 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Chat.Providers; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Chat { diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs index 82f5cfc72c..339664001e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Logging; using System; using Tgstation.Server.Host.Components.Chat.Commands; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Chat { diff --git a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs index c231ea3ef7..dd552f87ff 100644 --- a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Chat { diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index ee163ece83..9c5a127316 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -8,7 +8,7 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index 5609c3f08b..b6aed13e2b 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/DmbProvider.cs b/src/Tgstation.Server.Host/Components/DmbProvider.cs index 4efe95cd07..d93b12ed78 100644 --- a/src/Tgstation.Server.Host/Components/DmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/DmbProvider.cs @@ -1,5 +1,5 @@ using System; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 4f403b2797..06cd345bf7 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -11,6 +11,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 3c9466e415..6712b555e9 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -5,6 +5,7 @@ using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/Repository.cs b/src/Tgstation.Server.Host/Components/Repository.cs index 50364ca9b1..ed5f2676d3 100644 --- a/src/Tgstation.Server.Host/Components/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository.cs @@ -5,7 +5,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index e87d417cbd..e18bd24820 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -2,8 +2,8 @@ using System; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Core; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index bf472fcd33..b84535a1fa 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components.Watchdog diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 8040fe1464..a5bdd6196d 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -20,6 +20,7 @@ using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Controllers; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; diff --git a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs similarity index 96% rename from src/Tgstation.Server.Host/Core/DefaultIOManager.cs rename to src/Tgstation.Server.Host/IO/DefaultIOManager.cs index a5c3d59eb7..21cf9db515 100644 --- a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -7,7 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.IO { /// /// that resolves paths to @@ -220,5 +220,13 @@ namespace Tgstation.Server.Host.Core } return (IReadOnlyList)results; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task CreateSymlink(string target, string link, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + target = ResolvePath(target); + link = ResolvePath(link); + + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Core/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs similarity index 99% rename from src/Tgstation.Server.Host/Core/IIOManager.cs rename to src/Tgstation.Server.Host/IO/IIOManager.cs index 69a207e692..c317e70789 100644 --- a/src/Tgstation.Server.Host/Core/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.IO { /// /// Interface for using filesystems diff --git a/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs similarity index 98% rename from src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs rename to src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index 6faa6249ad..b0d61e969b 100644 --- a/src/Tgstation.Server.Host/Core/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.IO { /// /// For accessing the disk in a synchronous manner diff --git a/src/Tgstation.Server.Host/Core/ResolvingIOManager.cs b/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs similarity index 96% rename from src/Tgstation.Server.Host/Core/ResolvingIOManager.cs rename to src/Tgstation.Server.Host/IO/ResolvingIOManager.cs index f7d4bf2045..c10ac40b6c 100644 --- a/src/Tgstation.Server.Host/Core/ResolvingIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.IO { /// /// An that resolve relative paths from another to a subdirectory of that diff --git a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs similarity index 98% rename from src/Tgstation.Server.Host/Core/SynchronousIOManager.cs rename to src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index dc56319f11..052eff0a34 100644 --- a/src/Tgstation.Server.Host/Core/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Security.Cryptography; using System.Threading; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.IO { /// sealed class SynchronousIOManager : ISynchronousIOManager diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs index 8a4038dc53..f68029a95b 100644 --- a/src/Tgstation.Server.Host/NativeMethods.cs +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -13,5 +13,11 @@ namespace Tgstation.Server.Host /// [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); + + /// + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createsymboliclinkw + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 02f909a7b8..662d012233 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -33,7 +33,7 @@ - + @@ -45,6 +45,7 @@ + From 51a8d774abc31cff17dc1a3d05ccbd7c767beea0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 13:30:46 -0400 Subject: [PATCH 290/397] Implement SymlinkStaticFilesTo --- .../Components/Configuration.cs | 22 +++++++++++++++++-- .../IO/DefaultIOManager.cs | 19 ++++++++++++++-- src/Tgstation.Server.Host/IO/IIOManager.cs | 8 +++++++ .../IO/ISymlinkFactory.cs | 20 +++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 src/Tgstation.Server.Host/IO/ISymlinkFactory.cs diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index 9c5a127316..4c31b9eadd 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -33,6 +33,11 @@ namespace Tgstation.Server.Host.Components /// readonly ISynchronousIOManager synchronousIOManager; + /// + /// The for + /// + readonly ISymlinkFactory symlinkFactory; + /// /// The for /// @@ -43,11 +48,13 @@ namespace Tgstation.Server.Host.Components /// /// The value of /// The value of + /// The value of /// The value of - public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ILogger logger) + public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); + this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -171,7 +178,18 @@ namespace Tgstation.Server.Host.Components /// public Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken) { - throw new NotImplementedException(); + async Task SymlinkBase(bool files) + { + Task> task; + if (files) + task = ioManager.GetFiles(GameStaticFilesSubdirectory, cancellationToken); + else + task = ioManager.GetDirectories(GameStaticFilesSubdirectory, cancellationToken); + var entries = await task.ConfigureAwait(false); + await Task.WhenAll(entries.Select(x => symlinkFactory.CreateSymbolicLink(ioManager.ResolvePath(x), ioManager.ConcatPath(destination, x), cancellationToken))).ConfigureAwait(false); + } + + return Task.WhenAll(SymlinkBase(true), SymlinkBase(false)); } /// diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 21cf9db515..5a4bb9654f 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -213,10 +213,25 @@ namespace Tgstation.Server.Host.IO { path = ResolvePath(path); var results = new List(); - foreach(var I in Directory.EnumerateDirectories(path)) + cancellationToken.ThrowIfCancellationRequested(); + foreach (var I in Directory.EnumerateDirectories(path)) { - cancellationToken.ThrowIfCancellationRequested(); results.Add(I); + cancellationToken.ThrowIfCancellationRequested(); + } + return (IReadOnlyList)results; + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public Task> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + path = ResolvePath(path); + var results = new List(); + cancellationToken.ThrowIfCancellationRequested(); + foreach (var I in Directory.EnumerateFiles(path)) + { + results.Add(I); + cancellationToken.ThrowIfCancellationRequested(); } return (IReadOnlyList)results; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index c317e70789..e5b6cdefde 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -72,6 +72,14 @@ namespace Tgstation.Server.Host.IO /// A resulting in the directories in Task> GetDirectories(string path, CancellationToken cancellationToken); + /// + /// Returns file names in a given + /// + /// The path to search for files + /// The for the operation + /// A resulting in the files in + Task> GetFiles(string path, CancellationToken cancellationToken); + /// /// Writes some to a file at overwriting previous content /// diff --git a/src/Tgstation.Server.Host/IO/ISymlinkFactory.cs b/src/Tgstation.Server.Host/IO/ISymlinkFactory.cs new file mode 100644 index 0000000000..b92269404b --- /dev/null +++ b/src/Tgstation.Server.Host/IO/ISymlinkFactory.cs @@ -0,0 +1,20 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.IO +{ + /// + /// For creating filesystem symbolic links + /// + interface ISymlinkFactory + { + /// + /// Create a symbolic link + /// + /// The path to the hard target + /// The path to the link + /// The for the operation + /// A representing the running operation + Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken); + } +} From eabe364330a2119ec1c14ed4d6afb98d53bc7b38 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 13:31:52 -0400 Subject: [PATCH 291/397] Fix Configuration component construction --- .../Components/InstanceFactory.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 6712b555e9..391b06d5db 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -63,6 +63,11 @@ namespace Tgstation.Server.Host.Components /// readonly ISynchronousIOManager synchronousIOManager; + /// + /// The for the + /// + readonly ISymlinkFactory symlinkFactory; + /// /// Construct an /// @@ -76,7 +81,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -88,6 +94,7 @@ namespace Tgstation.Server.Host.Components this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); + this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); } /// @@ -109,7 +116,7 @@ namespace Tgstation.Server.Host.Components var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); IByond byond = null; - var configuration = new Configuration(configurationIoManager, synchronousIOManager, loggerFactory.CreateLogger()); + var configuration = new Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); var chat = chatFactory.CreateChat(); var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata); From d0c2851641863d5572d895160b2f052afd8850a4 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 13:39:08 -0400 Subject: [PATCH 292/397] Implement WindowsSymlinkFactory --- src/Tgstation.Server.Host/Core/Application.cs | 5 ++++ .../IO/WindowsSymlinkFactory.cs | 25 +++++++++++++++++++ src/Tgstation.Server.Host/NativeMethods.cs | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index a5bdd6196d..7d54b755cd 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -151,9 +151,14 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { services.AddSingleton(); + services.AddSingleton(); + } else + { services.AddSingleton(); + } services.AddSingleton(); services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs new file mode 100644 index 0000000000..09243bdcce --- /dev/null +++ b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.IO +{ + /// + /// for windows systems + /// + sealed class WindowsSymlinkFactory : ISymlinkFactory + { + /// + public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + //check if its not a file + var flags = File.Exists(targetPath) ? 0 : 1; //SYMBOLIC_LINK_FLAG_DIRECTORY + flags |= 2; //SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE on win10 1607+ + + if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) + throw new Win32Exception(Marshal.GetLastWin32Error()); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } +} diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs index f68029a95b..398185ab2a 100644 --- a/src/Tgstation.Server.Host/NativeMethods.cs +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -18,6 +18,6 @@ namespace Tgstation.Server.Host /// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createsymboliclinkw /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); + public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); } } From 89266ba5e1799be3cf3ca24d20c0c0035134e5ba Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 14:23:30 -0400 Subject: [PATCH 293/397] Add PosixSymlinkFactory --- src/Tgstation.Server.Host/Core/Application.cs | 1 + .../IO/PosixSymlinkFactory.cs | 27 +++++++++++++++++++ .../IO/WindowsSymlinkFactory.cs | 1 + 3 files changed, 29 insertions(+) create mode 100644 src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 7d54b755cd..ab686c1495 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -158,6 +158,7 @@ namespace Tgstation.Server.Host.Core else { services.AddSingleton(); + services.AddSingleton(); } services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs new file mode 100644 index 0000000000..90a00410be --- /dev/null +++ b/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs @@ -0,0 +1,27 @@ +using Mono.Unix; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.IO +{ + /// + /// for posix systems + /// + sealed class PosixSymlinkFactory : ISymlinkFactory + { + /// + public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + UnixFileSystemInfo fsInfo; + var isFile = File.Exists(targetPath); + cancellationToken.ThrowIfCancellationRequested(); + if (isFile) + fsInfo = new UnixFileInfo(targetPath); + else + fsInfo = new UnixDirectoryInfo(targetPath); + cancellationToken.ThrowIfCancellationRequested(); + fsInfo.CreateSymbolicLink(linkPath); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } +} diff --git a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs index 09243bdcce..824b5375f5 100644 --- a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs @@ -18,6 +18,7 @@ namespace Tgstation.Server.Host.IO var flags = File.Exists(targetPath) ? 0 : 1; //SYMBOLIC_LINK_FLAG_DIRECTORY flags |= 2; //SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE on win10 1607+ + cancellationToken.ThrowIfCancellationRequested(); if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) throw new Win32Exception(Marshal.GetLastWin32Error()); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); From 0d96f696178fa6cf9a9cc6dc288ceb66521c4e81 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 14:32:11 -0400 Subject: [PATCH 294/397] Fix build --- src/Tgstation.Server.Host/Components/Configuration.cs | 2 +- src/Tgstation.Server.Host/Components/InstanceManager.cs | 1 + src/Tgstation.Server.Host/Controllers/InstanceController.cs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index 4c31b9eadd..23af8efb62 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Components sealed class Configuration : IConfiguration { const string CodeModificationsSubdirectory = "CodeModifications"; - const string EventScriptsSubdirectory = "EventScripts"; + //const string EventScriptsSubdirectory = "EventScripts"; const string GameStaticFilesSubdirectory = "GameStaticFiles"; const string CodeModificationsHeadFile = "HeadInclude.dm"; diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 1d49f58fa6..1d9671acff 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 95e59d61f8..e7f38e8a01 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; From e79988c2f120cdd21d5f3bd4249a6455e1854f95 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 15:07:40 -0400 Subject: [PATCH 295/397] Add skeleton BYOND implementation --- src/Tgstation.Server.Host/Components/Byond.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/Byond.cs diff --git a/src/Tgstation.Server.Host/Components/Byond.cs b/src/Tgstation.Server.Host/Components/Byond.cs new file mode 100644 index 0000000000..1e3e5e39df --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Byond.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class Byond : IByond + { + /// + /// The for + /// + readonly IIOManager ioManager; + + /// + /// The for + /// + readonly ILogger logger; + + /// + /// List of installed BYOND s + /// + readonly List installedVersions; + + /// + /// Construct + /// + /// The value of + /// The value of + public Byond(IIOManager ioManager, ILogger logger) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + public Task ChangeVersion(Version version, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task ClearCache(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task GetVersion(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public IByondExecutableLock UseExecutables(Version requiredVersion) + { + throw new NotImplementedException(); + } + } +} From adbc02a7c1604d4f0fa0091d6ca78bbc6687646b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 15:09:14 -0400 Subject: [PATCH 296/397] Delete server settings. Remove soft stop. Posting an Administration API model should always reboot it --- .../Models/Administration.cs | 8 +------ .../Models/Internal/ServerSettings.cs | 23 ------------------- .../Models/DatabaseContext.cs | 15 ------------ .../Models/IDatabaseContext.cs | 7 ------ .../Models/ServerSettings.cs | 11 --------- 5 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs delete mode 100644 src/Tgstation.Server.Host/Models/ServerSettings.cs diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 640a5c65ac..9e51bd7d3c 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -5,14 +5,8 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// - public sealed class Administration : Internal.ServerSettings + public sealed class Administration { - /// - /// If the instances will not be stopped when the server exits. Resets to when the server restarts - /// - [Permissions(ReadRight = AdministrationRights.SoftStop, WriteRight = AdministrationRights.SoftStop)] - public bool SoftStop { get; set; } - /// /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is higher than 's the update cannot be applied due to API changes /// diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs deleted file mode 100644 index 3ab52adef5..0000000000 --- a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Tgstation.Server.Api.Rights; - -namespace Tgstation.Server.Api.Models.Internal -{ - /// - /// Metadata about an installation - /// - [Model(RightsType.Administration)] - public class ServerSettings - { - /// - /// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information. - /// - [Permissions(ReadRight = AdministrationRights.ChangeTelemetry, WriteRight = AdministrationRights.ChangeTelemetry)] - public bool EnableTelemetry { get; set; } - - /// - /// The git repository URL to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to upstream pulls will be disabled entirely. Should be https://github.com/tgstation/tgstation-server or a fork of it - /// - [Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)] - public string UpstreamRepository { get; set; } - } -} diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 44099444d8..c9bacb6086 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -14,9 +14,6 @@ namespace Tgstation.Server.Host.Models /// abstract class DatabaseContext : DbContext, IDatabaseContext where TParentContext : DbContext { - /// - public DbSet ServerSettings { get; set; } - /// public DbSet Users { get; set; } @@ -126,18 +123,6 @@ namespace Tgstation.Server.Host.Models base.OnConfiguring(optionsBuilder); } - /// - public async Task GetServerSettings(CancellationToken cancellationToken) - { - var settings = await ServerSettings.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); - if (settings == default(ServerSettings)) - { - settings = new ServerSettings(); - ServerSettings.Add(settings); - } - return settings; - } - /// public async Task Initialize(CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 9e8d906905..0d3814a284 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -59,13 +59,6 @@ namespace Tgstation.Server.Host.Models /// DbSet RepositorySettings { get; set; } - /// - /// Get the in the - /// - /// The for the operation - /// A resulting in the in the - Task GetServerSettings(CancellationToken cancellationToken); - /// /// Saves changes made to the /// diff --git a/src/Tgstation.Server.Host/Models/ServerSettings.cs b/src/Tgstation.Server.Host/Models/ServerSettings.cs deleted file mode 100644 index 4326d9f563..0000000000 --- a/src/Tgstation.Server.Host/Models/ServerSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Tgstation.Server.Host.Models -{ - /// - public sealed class ServerSettings : Api.Models.Internal.ServerSettings - { - /// - /// The row Id - /// - public long Id { get; set; } - } -} From 7c47cbe5656ff7ce1e1ebf3cd1e2e1af5fac0a19 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 15:10:18 -0400 Subject: [PATCH 297/397] Removes useless field --- src/Tgstation.Server.Api/Models/Byond.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index 6654babe0a..d9a87d4643 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -20,11 +20,5 @@ namespace Tgstation.Server.Api.Models /// [Permissions(ReadRight = ByondRights.ReadInstalled, WriteRight = ByondRights.ChangeVersion)] public Version Version { get; set; } - - /// - /// The of the that's currently running the game servers - /// - [Permissions(DenyWrite = true, ReadRight = ByondRights.ReadPrevious)] - public Version PreviousVersion { get; set; } } } From 7f77d7f9ab080eb92ef72d7b4a68b4dc44773623 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 15:41:53 -0400 Subject: [PATCH 298/397] BYOND believable --- src/Tgstation.Server.Api/Models/Byond.cs | 6 - .../Models/ByondStatus.cs | 39 ------ src/Tgstation.Server.Host/Components/Byond.cs | 63 ---------- .../Components/ByondManager.cs | 113 ++++++++++++++++++ .../Components/DreamMaker.cs | 6 +- .../Components/IByondInstaller.cs | 30 +++++ .../{IByond.cs => IByondManager.cs} | 6 +- .../Components/IInstance.cs | 4 +- .../Components/Instance.cs | 6 +- .../Components/InstanceFactory.cs | 2 +- .../Watchdog/SessionControllerFactory.cs | 6 +- 11 files changed, 158 insertions(+), 123 deletions(-) delete mode 100644 src/Tgstation.Server.Api/Models/ByondStatus.cs delete mode 100644 src/Tgstation.Server.Host/Components/Byond.cs create mode 100644 src/Tgstation.Server.Host/Components/ByondManager.cs create mode 100644 src/Tgstation.Server.Host/Components/IByondInstaller.cs rename src/Tgstation.Server.Host/Components/{IByond.cs => IByondManager.cs} (91%) diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index d9a87d4643..b0b1647831 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -9,12 +9,6 @@ namespace Tgstation.Server.Api.Models [Model(RightsType.Byond, RequiresInstance = true)] public sealed class Byond { - /// - /// The for the installation - /// - [Permissions(DenyWrite = true, ReadRight = ByondRights.ReadStatus)] - public ByondStatus ByondStatus { get; set; } - /// /// The of the installation used for new compiles. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers /// diff --git a/src/Tgstation.Server.Api/Models/ByondStatus.cs b/src/Tgstation.Server.Api/Models/ByondStatus.cs deleted file mode 100644 index 638a91e475..0000000000 --- a/src/Tgstation.Server.Api/Models/ByondStatus.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Tgstation.Server.Api.Models -{ - /// - /// The status of a update job - /// -#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names - public enum ByondStatus -#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names - { - /// - /// No update in progress - /// - Idle, - /// - /// Preparing to update - /// - Starting, - /// - /// Revision is downloading - /// - Downloading, - /// - /// Revision is deflating - /// - Staging, - /// - /// Revision is ready and waiting for DreamDaemon reboot - /// - Staged, - /// - /// Revision is being applied - /// - Updating, - /// - /// User does not have permission to view the - /// - Hidden, - } -} diff --git a/src/Tgstation.Server.Host/Components/Byond.cs b/src/Tgstation.Server.Host/Components/Byond.cs deleted file mode 100644 index 1e3e5e39df..0000000000 --- a/src/Tgstation.Server.Host/Components/Byond.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Host.IO; - -namespace Tgstation.Server.Host.Components -{ - /// - sealed class Byond : IByond - { - /// - /// The for - /// - readonly IIOManager ioManager; - - /// - /// The for - /// - readonly ILogger logger; - - /// - /// List of installed BYOND s - /// - readonly List installedVersions; - - /// - /// Construct - /// - /// The value of - /// The value of - public Byond(IIOManager ioManager, ILogger logger) - { - this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - public Task ChangeVersion(Version version, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public Task ClearCache(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public Task GetVersion(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public IByondExecutableLock UseExecutables(Version requiredVersion) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs new file mode 100644 index 0000000000..aff60de879 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -0,0 +1,113 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class ByondManager : IByondManager + { + const string VersionFileName = "Version.txt"; + + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// The for the + /// + readonly IByondInstaller byondInstaller; + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Map of byond s to s that complete when they are installed + /// + readonly Dictionary installedVersions; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + public ByondManager(IIOManager ioManager, IByondInstaller byondInstaller, ILogger logger) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + static string VersionKey(Version version) => new Version(version.Major, version.Minor).ToString(); + + async Task InstallVersion(Version version, CancellationToken cancellationToken) + { + var ourTcs = new TaskCompletionSource(); + Task inProgressTask; + + var versionKey = VersionKey(version); + bool installed; + lock (installedVersions) + { + installed = installedVersions.TryGetValue(versionKey, out inProgressTask); + if (!installed) + installedVersions.Add(versionKey, ourTcs.Task); + } + if(installed) + using (cancellationToken.Register(() => ourTcs.SetCanceled())) + { + await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false); + return; + } + + var downloadTask = byondInstaller.DownloadVersion(version, cancellationToken); + + //okay up to us to install it then + await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false); + await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false); + + var resolvedPath = ioManager.ResolvePath(versionKey); + using (var zipBytes = await downloadTask.ConfigureAwait(false)) + using (var archive = new ZipArchive(zipBytes)) + await Task.Factory.StartNew(() => archive.ExtractToDirectory(resolvedPath), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + + await byondInstaller.InstallByond(resolvedPath, cancellationToken).ConfigureAwait(false); + + //make sure to do this last because this is what tells us we have a valid version + await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); + } + + /// + public Task ChangeVersion(Version version, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task ClearCache(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task GetVersion(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public IByondExecutableLock UseExecutables(Version requiredVersion) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 06cd345bf7..58dbccfd38 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -39,9 +39,9 @@ namespace Tgstation.Server.Host.Components public CompilerStatus Status { get; private set; } /// - /// The for + /// The for /// - readonly IByond byond; + readonly IByondManager byond; /// /// The for /// @@ -82,7 +82,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public DreamMaker(IByond byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger logger) + public DreamMaker(IByondManager byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger logger) { this.byond = byond; this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); diff --git a/src/Tgstation.Server.Host/Components/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/IByondInstaller.cs new file mode 100644 index 0000000000..98a8e4831d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IByondInstaller.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using Stream = System.IO.Stream; + +namespace Tgstation.Server.Host.Components +{ + /// + /// For downloading and installing BYOND extractions + /// + interface IByondInstaller + { + /// + /// Download a given BYOND + /// + /// The of BYOND to download + /// The for the operation + /// A resulting in a of the zipfile + Task DownloadVersion(Version version, CancellationToken cancellationToken); + + /// + /// Does actions necessary to get an extracted BYOND installation working + /// + /// The path to the BYOND installation + /// The for the operation + /// + Task InstallByond(string path, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByondManager.cs similarity index 91% rename from src/Tgstation.Server.Host/Components/IByond.cs rename to src/Tgstation.Server.Host/Components/IByondManager.cs index 105d6dac74..a0226edd64 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByondManager.cs @@ -7,17 +7,17 @@ namespace Tgstation.Server.Host.Components /// /// For managing the BYOND installation /// - public interface IByond + public interface IByondManager { /// - /// Change the current BYOND version + /// Change the active BYOND version /// /// The new /// The for the operation Task ChangeVersion(Version version, CancellationToken cancellationToken); /// - /// Get the currently installed BYOND version + /// Get the currently active BYOND version /// /// The for the operation /// The current BYOND version diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 9868430342..ffcf471681 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -16,9 +16,9 @@ namespace Tgstation.Server.Host.Components IRepositoryManager RepositoryManager { get; } /// - /// The for the + /// The for the /// - IByond Byond { get; } + IByondManager ByondManager { get; } /// /// The for the diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index d607053ab2..80e0a8bf3a 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components public IRepositoryManager RepositoryManager { get; } /// - public IByond Byond { get; } + public IByondManager ByondManager { get; } /// public IDreamMaker DreamMaker { get; } @@ -59,11 +59,11 @@ namespace Tgstation.Server.Host.Components /// CancellationTokenSource timerCts; - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory) + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); - Byond = byond ?? throw new ArgumentNullException(nameof(byond)); + ByondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager)); DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)); watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); Chat = chat ?? throw new ArgumentNullException(nameof(chat)); diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 391b06d5db..ec9021ff1d 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Components var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); - IByond byond = null; + IByondManager byond = null; var configuration = new Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); var chat = chatFactory.CreateChat(); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index b84535a1fa..d5f6063c75 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -24,9 +24,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly IExecutor executor; /// - /// The for the + /// The for the /// - readonly IByond byond; + readonly IByondManager byond; /// /// The for the @@ -81,7 +81,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public SessionControllerFactory(IExecutor executor, IByond byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Models.Instance instance) + public SessionControllerFactory(IExecutor executor, IByondManager byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Models.Instance instance) { this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); From 902333674459de5c22d99fbc7444ecbfdfce8dd0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 16:14:04 -0400 Subject: [PATCH 299/397] More ByondManager stuff --- .../Components/ByondExecutableLock.cs | 17 +++++ .../Components/ByondManager.cs | 69 ++++++++++++++++--- .../Components/IByondInstaller.cs | 19 ++++- .../Components/IByondManager.cs | 26 +++---- 4 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/ByondExecutableLock.cs diff --git a/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs new file mode 100644 index 0000000000..2e89dacb38 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs @@ -0,0 +1,17 @@ +using System; + +namespace Tgstation.Server.Host.Components +{ + sealed class ByondExecutableLock : IByondExecutableLock + { + public Version Version { get; set; } + + public string DreamDaemonPath { get; set; } + + public string DreamMakerPath { get; set; } + + public void Dispose() { } + + public void DoNotDeleteThisSession() { } + } +} diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs index aff60de879..1c00689b34 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO.Compression; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -13,6 +14,12 @@ namespace Tgstation.Server.Host.Components sealed class ByondManager : IByondManager { const string VersionFileName = "Version.txt"; + const string ActiveVersionFileName = "ActiveVersion.txt"; + + const string BinPath = "byond/bin"; + + /// + public Version ActiveVersion { get; private set; } /// /// The for the @@ -87,27 +94,69 @@ namespace Tgstation.Server.Host.Components } /// - public Task ChangeVersion(Version version, CancellationToken cancellationToken) + public async Task ChangeVersion(Version version, CancellationToken cancellationToken) { - throw new NotImplementedException(); + await InstallVersion(version, cancellationToken).ConfigureAwait(false); + await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); + ActiveVersion = version; } /// - public Task ClearCache(CancellationToken cancellationToken) + public async Task UseExecutables(Version requiredVersion, CancellationToken cancellationToken) { - throw new NotImplementedException(); + var versionToUse = requiredVersion ?? ActiveVersion; + if (versionToUse == null) + throw new InvalidOperationException("No BYOND versions installed!"); + await InstallVersion(requiredVersion, cancellationToken).ConfigureAwait(false); + + var versionKey = VersionKey(versionToUse); + + return new ByondExecutableLock + { + DreamDaemonPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, byondInstaller.DreamDaemonName)), + DreamMakerPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, byondInstaller.DreamMakerName)), + Version = versionToUse + }; } /// - public Task GetVersion(CancellationToken cancellationToken) + public async Task StartAsync(CancellationToken cancellationToken) { - throw new NotImplementedException(); + var cacheCleanTask = byondInstaller.CleanCache(cancellationToken); + + var activeVersionBytesTask = ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken); + + var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false); + + async Task ReadVersion(string path) + { + var bytes = await ioManager.ReadAllBytes(ioManager.ConcatPath(path, VersionFileName), cancellationToken).ConfigureAwait(false); + var text = Encoding.UTF8.GetString(bytes); + if (Version.TryParse(text, out var version)) + { + var key = VersionKey(version); + lock (installedVersions) + if (!installedVersions.ContainsKey(key)) + { + installedVersions.Add(key, Task.CompletedTask); + return; + } + } + await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); + }; + + await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false); + + var activeVersionString = Encoding.UTF8.GetString(await activeVersionBytesTask.ConfigureAwait(false)); + if (Version.TryParse(activeVersionString, out var activeVersion)) + ActiveVersion = activeVersion; + else + await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); + + await cacheCleanTask.ConfigureAwait(false); } /// - public IByondExecutableLock UseExecutables(Version requiredVersion) - { - throw new NotImplementedException(); - } + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } } diff --git a/src/Tgstation.Server.Host/Components/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/IByondInstaller.cs index 98a8e4831d..2779e19969 100644 --- a/src/Tgstation.Server.Host/Components/IByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/IByondInstaller.cs @@ -7,10 +7,20 @@ using Stream = System.IO.Stream; namespace Tgstation.Server.Host.Components { /// - /// For downloading and installing BYOND extractions + /// For downloading and installing BYOND extractions for a given system /// interface IByondInstaller { + /// + /// Get the file name of the DreamDaemon executable + /// + string DreamDaemonName { get; } + + /// + /// Get the file name of the DreamMaker executable + /// + string DreamMakerName { get; } + /// /// Download a given BYOND /// @@ -26,5 +36,12 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// Task InstallByond(string path, CancellationToken cancellationToken); + + /// + /// Attempts to cleans the BYOND cache folder for the system + /// + /// The for the operation + /// A representing the running operation + Task CleanCache(CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IByondManager.cs b/src/Tgstation.Server.Host/Components/IByondManager.cs index a0226edd64..c6ac12119e 100644 --- a/src/Tgstation.Server.Host/Components/IByondManager.cs +++ b/src/Tgstation.Server.Host/Components/IByondManager.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Hosting; +using System; using System.Threading; using System.Threading.Tasks; @@ -7,8 +8,13 @@ namespace Tgstation.Server.Host.Components /// /// For managing the BYOND installation /// - public interface IByondManager + public interface IByondManager : IHostedService { + /// + /// The currently active BYOND version + /// + Version ActiveVersion { get; } + /// /// Change the active BYOND version /// @@ -16,24 +22,12 @@ namespace Tgstation.Server.Host.Components /// The for the operation Task ChangeVersion(Version version, CancellationToken cancellationToken); - /// - /// Get the currently active BYOND version - /// - /// The for the operation - /// The current BYOND version - Task GetVersion(CancellationToken cancellationToken); - /// /// Lock the current installation's location and return a /// /// The BYOND required - IByondExecutableLock UseExecutables(Version requiredVersion); - - /// - /// Clears the cache folder - /// /// The for the operation - /// A representing the running operation - Task ClearCache(CancellationToken cancellationToken); + /// A resulting in the requested + Task UseExecutables(Version requiredVersion, CancellationToken cancellationToken); } } \ No newline at end of file From e98dde5089c5a8a05c8269b143eb13d7b0fc55d0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 16:15:48 -0400 Subject: [PATCH 300/397] Docs for ByondExecutableLock --- .../Components/ByondExecutableLock.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs index 2e89dacb38..2be7ab498a 100644 --- a/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs @@ -2,16 +2,25 @@ namespace Tgstation.Server.Host.Components { + /// sealed class ByondExecutableLock : IByondExecutableLock { + /// public Version Version { get; set; } + /// public string DreamDaemonPath { get; set; } + /// public string DreamMakerPath { get; set; } + //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 + + /// public void Dispose() { } + /// public void DoNotDeleteThisSession() { } } } From a56c8b20f9a689b33bbcd287651f38a09bb34d1b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 16:32:34 -0400 Subject: [PATCH 301/397] WindowsByondInstaller stuff --- .../Components/ByondManager.cs | 4 +- .../Components/IByondInstaller.cs | 6 +-- .../Components/WindowsByondInstaller.cs | 47 +++++++++++++++++++ .../IO/DefaultIOManager.cs | 9 ++++ src/Tgstation.Server.Host/IO/IIOManager.cs | 11 ++++- 5 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs index 1c00689b34..9613157065 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -8,6 +8,8 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.IO; +using MemoryStream = System.IO.MemoryStream; + namespace Tgstation.Server.Host.Components { /// @@ -83,7 +85,7 @@ namespace Tgstation.Server.Host.Components await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false); var resolvedPath = ioManager.ResolvePath(versionKey); - using (var zipBytes = await downloadTask.ConfigureAwait(false)) + using (var zipBytes = new MemoryStream(await downloadTask.ConfigureAwait(false))) using (var archive = new ZipArchive(zipBytes)) await Task.Factory.StartNew(() => archive.ExtractToDirectory(resolvedPath), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/IByondInstaller.cs index 2779e19969..4b3b6996d3 100644 --- a/src/Tgstation.Server.Host/Components/IByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/IByondInstaller.cs @@ -2,8 +2,6 @@ using System.Threading; using System.Threading.Tasks; -using Stream = System.IO.Stream; - namespace Tgstation.Server.Host.Components { /// @@ -26,8 +24,8 @@ namespace Tgstation.Server.Host.Components /// /// The of BYOND to download /// The for the operation - /// A resulting in a of the zipfile - Task DownloadVersion(Version version, CancellationToken cancellationToken); + /// A resulting in the s of the zipfile + Task DownloadVersion(Version version, CancellationToken cancellationToken); /// /// Does actions necessary to get an extracted BYOND installation working diff --git a/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs new file mode 100644 index 0000000000..de1637fd34 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; + +namespace Tgstation.Server.Host.Components +{ + /// + /// for windows systems + /// + sealed class WindowsByondInstaller : IByondInstaller + { + /// + /// The URL format string for getting BYOND windows version {0}.{1} zipfile + /// + const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; + + /// + public string DreamDaemonName => "dreamdaemon.exe"; + + /// + public string DreamMakerName => "dm.exe"; + + /// + /// The for the + /// + readonly IIOManager ioManager; + + public Task CleanCache(CancellationToken cancellationToken) => ioManager.DeleteDirectory(ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "byond/cache"), cancellationToken); + + public Task DownloadVersion(Version version, CancellationToken cancellationToken) + { + var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Major); + + return ioManager.DownloadFile(new Uri(url), cancellationToken); + } + + public Task InstallByond(string path, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 5a4bb9654f..793b5881e2 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -243,5 +244,13 @@ namespace Tgstation.Server.Host.IO link = ResolvePath(link); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + + /// + public async Task DownloadFile(Uri url, CancellationToken cancellationToken) + { + using (var wc = new WebClient()) + using (cancellationToken.Register(() => wc.CancelAsync())) + return await wc.DownloadDataTaskAsync(url).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index e5b6cdefde..ad320805d3 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -153,5 +154,13 @@ namespace Tgstation.Server.Host.IO /// A for the operation /// A representing the running operation Task MoveFile(string source, string destination, CancellationToken cancellationToken); + + /// + /// Downloads a file from + /// + /// The URL to download + /// A for the operation + /// A resulting in the s of the downloaded file + Task DownloadFile(Uri url, CancellationToken cancellationToken); } } From 2b1e65a496427fc171b56b58d27115a2652c96b9 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 16:45:18 -0400 Subject: [PATCH 302/397] Finish writing WindowsByondInstaller --- .../Components/ByondManager.cs | 2 +- .../Components/IByondInstaller.cs | 3 +- .../Components/WindowsByondInstaller.cs | 86 ++++++++++++++++++- src/Tgstation.Server.Host/Core/Application.cs | 1 + 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs index 9613157065..67e1c3f110 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -89,7 +89,7 @@ namespace Tgstation.Server.Host.Components using (var archive = new ZipArchive(zipBytes)) await Task.Factory.StartNew(() => archive.ExtractToDirectory(resolvedPath), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - await byondInstaller.InstallByond(resolvedPath, cancellationToken).ConfigureAwait(false); + await byondInstaller.InstallByond(resolvedPath, version, cancellationToken).ConfigureAwait(false); //make sure to do this last because this is what tells us we have a valid version await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/IByondInstaller.cs index 4b3b6996d3..9ad7e26510 100644 --- a/src/Tgstation.Server.Host/Components/IByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/IByondInstaller.cs @@ -31,9 +31,10 @@ namespace Tgstation.Server.Host.Components /// Does actions necessary to get an extracted BYOND installation working /// /// The path to the BYOND installation + /// The of BYOND being installed /// The for the operation /// - Task InstallByond(string path, CancellationToken cancellationToken); + Task InstallByond(string path, Version version, CancellationToken cancellationToken); /// /// Attempts to cleans the BYOND cache folder for the system diff --git a/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs index de1637fd34..1a5274eae4 100644 --- a/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs @@ -1,7 +1,6 @@ using System; -using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; -using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -18,6 +17,18 @@ namespace Tgstation.Server.Host.Components /// The URL format string for getting BYOND windows version {0}.{1} zipfile /// const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; + /// + /// BYOND's DreamDaemon config file in the cfg modification directory + /// + const string ByondDDConfig = "byond/config/daemon.txt"; + /// + /// Setting to add to to suppress an invisible user prompt for running a trusted mode .dmb + /// + const string ByondNoPromptTrustedMode = "trusted-check 0"; + /// + /// The directory that contains the BYOND directx redistributable + /// + const string ByondDXDir = "byond/directx"; /// public string DreamDaemonName => "dreamdaemon.exe"; @@ -30,8 +41,26 @@ namespace Tgstation.Server.Host.Components /// readonly IIOManager ioManager; + /// + /// If DirectX was installed + /// + bool installedDirectX; + + /// + /// Construct a + /// + /// The value of + public WindowsByondInstaller(IIOManager ioManager) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + + installedDirectX = false; + } + + /// public Task CleanCache(CancellationToken cancellationToken) => ioManager.DeleteDirectory(ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "byond/cache"), cancellationToken); + /// public Task DownloadVersion(Version version, CancellationToken cancellationToken) { var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Major); @@ -39,9 +68,58 @@ namespace Tgstation.Server.Host.Components return ioManager.DownloadFile(new Uri(url), cancellationToken); } - public Task InstallByond(string path, CancellationToken cancellationToken) + /// + public async Task InstallByond(string path, Version version, CancellationToken cancellationToken) { - throw new NotImplementedException(); + var setNoPromptTrustedModeTask = ioManager.WriteAllBytes(ByondDDConfig, Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken); + + //after this version lummox made DD depend of directx lol + if (version.Major >= 512 && version.Minor >= 1427 && Monitor.TryEnter(this)) + try + { + if (!installedDirectX) + //always install it, it's pretty fast and will do better redundancy checking than us + using (var p = new Process()) + { + p.StartInfo.Arguments = "/silent"; + var rbdx = ioManager.ConcatPath(path, ByondDXDir); + p.StartInfo.FileName = rbdx + "/DXSETUP.exe"; + p.StartInfo.UseShellExecute = false; + p.StartInfo.WorkingDirectory = rbdx; + p.EnableRaisingEvents = true; + var tcs = new TaskCompletionSource(); + p.Exited += (a, b) => tcs.SetResult(null); + try + { + p.Start(); + using (cancellationToken.Register(() => + { + p.Kill(); + tcs.SetCanceled(); + })) + await tcs.Task.ConfigureAwait(false); + } + finally + { + try + { + p.Kill(); + p.WaitForExit(); + } + catch (InvalidOperationException) { } + } + + if (p.ExitCode != 0) + throw new Exception("Failed to install included DirectX! Exit code: " + p.ExitCode); + installedDirectX = true; + } + } + finally + { + Monitor.Exit(this); + } + + await setNoPromptTrustedModeTask.ConfigureAwait(false); } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index ab686c1495..35cde358b6 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -154,6 +154,7 @@ namespace Tgstation.Server.Host.Core { services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } else { From 39680073858ba6faed9702aa79aa19fe395f8d26 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 23 Jul 2018 17:01:52 -0400 Subject: [PATCH 303/397] Implement PosixByondInstaller --- .../Components/PosixByondInstaller.cs | 93 +++++++++++++++++++ .../Components/WindowsByondInstaller.cs | 26 +++++- src/Tgstation.Server.Host/Core/Application.cs | 1 + 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/PosixByondInstaller.cs diff --git a/src/Tgstation.Server.Host/Components/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/PosixByondInstaller.cs new file mode 100644 index 0000000000..2a3e5cd44d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/PosixByondInstaller.cs @@ -0,0 +1,93 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Globalization; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; + +namespace Tgstation.Server.Host.Components +{ + /// + /// for Posix systems + /// + sealed class PosixByondInstaller : IByondInstaller + { + /// + /// The URL format string for getting BYOND linux version {0}.{1} zipfile + /// + const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip"; + /// + /// Path to the BYOND cache + /// + const string ByondCachePath = "~/.byond"; //TODO: Verify this is correct!!!!! + + /// + public string DreamDaemonName => "DreamDaemon"; + + /// + public string DreamMakerName => "DreamMaker"; + + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Construct a + /// + /// The value of + /// The value of + public PosixByondInstaller(IIOManager ioManager, ILogger logger) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + public async Task CleanCache(CancellationToken cancellationToken) + { + try + { + await ioManager.DeleteDirectory(ByondCachePath, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning("Error deleting BYOND cache! Exception: {0}", e); + } + } + + /// + public async Task DownloadVersion(Version version, CancellationToken cancellationToken) + { + var ourVersion = version; + //lummox is annoying and doesn't like to post linux versions if nothing changed in DreamDaemon/DM + //if this for's exit condition ever triggers, i get to say I told you so + Exception lastException = null; + for (var I = 0; I < 5 && ourVersion.Minor >= 1; ++I, ourVersion = new Version(ourVersion.Major, ourVersion.Minor)) + { + try + { + var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, ourVersion.Major, ourVersion.Minor); + + return await ioManager.DownloadFile(new Uri(url), cancellationToken).ConfigureAwait(false); + } + catch (WebException e) + { + if (!(e.Status == WebExceptionStatus.ProtocolError && e.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound)) + throw; + lastException = e; + } + } + throw lastException; + } + + /// + public Task InstallByond(string path, Version version, CancellationToken cancellationToken) => Task.CompletedTask; + } +} diff --git a/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs index 1a5274eae4..6b102ace09 100644 --- a/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using System.Diagnostics; using System.Globalization; using System.Text; @@ -41,6 +42,11 @@ namespace Tgstation.Server.Host.Components /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly ILogger logger; + /// /// If DirectX was installed /// @@ -50,20 +56,32 @@ namespace Tgstation.Server.Host.Components /// Construct a /// /// The value of - public WindowsByondInstaller(IIOManager ioManager) + /// The value of + public WindowsByondInstaller(IIOManager ioManager, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); installedDirectX = false; } /// - public Task CleanCache(CancellationToken cancellationToken) => ioManager.DeleteDirectory(ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "byond/cache"), cancellationToken); + public async Task CleanCache(CancellationToken cancellationToken) + { + try + { + await ioManager.DeleteDirectory(ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "byond/cache"), cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning("Error deleting BYOND cache! Exception: {0}", e); + } + } /// public Task DownloadVersion(Version version, CancellationToken cancellationToken) { - var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Major); + var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor); return ioManager.DownloadFile(new Uri(url), cancellationToken); } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 35cde358b6..aee2ab7feb 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -160,6 +160,7 @@ namespace Tgstation.Server.Host.Core { services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } services.AddSingleton(); From 6425e8c37e2dd98484e1eb3b559db7221d9ca1ae Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 23 Jul 2018 21:46:38 -0400 Subject: [PATCH 304/397] Fix build errors --- src/Tgstation.Server.Host/Components/DreamMaker.cs | 2 +- .../Components/Watchdog/SessionControllerFactory.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 58dbccfd38..7d1c19ba36 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -274,7 +274,7 @@ namespace Tgstation.Server.Host.Components //run compiler, verify api bool ddVerified; - using (var byondLock = byond.UseExecutables(null)) + using (var byondLock = await byond.UseExecutables(null, cancellationToken).ConfigureAwait(false)) { job.ByondVersion = byondLock.Version.ToString(); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index d5f6063c75..9b48f51f8d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -142,7 +142,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false); try { - var byondLock = currentByondLock ?? byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion)); + var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false); try { //more sanitization here cause it uses the same scheme @@ -190,7 +190,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatJsonTrackingContext = await chat.TrackJsons(basePath, reattachInformation.ChatChannelsJson, reattachInformation.ChatCommandsJson, cancellationToken).ConfigureAwait(false); try { - var byondLock = byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion)); + var byondLock = await byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false); try { var session = executor.AttachToDreamDaemon(reattachInformation.ProcessId, byondLock); From e76061ae59e648af82cd683a603851b4118e2ae8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 23 Jul 2018 22:24:53 -0400 Subject: [PATCH 305/397] Actually fix the build --- src/Tgstation.Server.Host/Models/DatabaseSeeder.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index 772368aed3..37bf2f1c0f 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -60,12 +60,6 @@ namespace Tgstation.Server.Host.Models public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken) { SeedAdminUser(databaseContext); - - var serverSettings = await databaseContext.GetServerSettings(cancellationToken).ConfigureAwait(false); - - serverSettings.EnableTelemetry = true; - serverSettings.UpstreamRepository = DefaultUpstreamRepository; - await databaseContext.Save(cancellationToken).ConfigureAwait(false); } From 6aa35d25430a0c6fba7ac7880d25deab33104c93 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 23 Jul 2018 22:40:01 -0400 Subject: [PATCH 306/397] Actually actually fix the build --- src/Tgstation.Server.Client/Components/IByondClient.cs | 5 ++--- src/Tgstation.Server.Host/Components/ByondManager.cs | 2 ++ src/Tgstation.Server.Host/Components/Instance.cs | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs index f7e064e0b2..403f6dd34a 100644 --- a/src/Tgstation.Server.Client/Components/IByondClient.cs +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -21,10 +21,9 @@ namespace Tgstation.Server.Client.Components /// /// Updates the installed BYOND /// - /// The to update - /// Optional taking a to run when it changes + /// The to set to active /// The for the operation /// A representing the running operation - Task Update(Byond byond, Action progressCallback, CancellationToken cancellationToken); + Task SetActiveVersion(Version version, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs index 67e1c3f110..51eed66c52 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -54,6 +54,8 @@ namespace Tgstation.Server.Host.Components this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + installedVersions = new Dictionary(); } static string VersionKey(Version version) => new Version(version.Major, version.Minor).ToString(); diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 80e0a8bf3a..fea1f1b57f 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -125,10 +125,10 @@ namespace Tgstation.Server.Host.Components } /// - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken)); + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), ByondManager.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken)); /// - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken)); /// public async Task SetAutoUpdateInterval(int? newInterval) From 27de23a4b221f8b4f93ca86b24a3945129426478 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 23 Jul 2018 22:57:11 -0400 Subject: [PATCH 307/397] How hard is it to actually run a release build, man? --- src/Tgstation.Server.Api/Models/Byond.cs | 2 +- .../Rights/AdministrationRights.cs | 16 ++++------------ src/Tgstation.Server.Api/Rights/ByondRights.cs | 18 +++--------------- .../Components/IByondClient.cs | 2 +- .../Components/ByondManager.cs | 4 ++-- .../IO/SynchronousIOManager.cs | 18 +++++++++++------- .../Models/DatabaseSeeder.cs | 5 ----- .../Security/TestAuthenticationContext.cs | 2 +- 8 files changed, 23 insertions(+), 44 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index b0b1647831..17f5b1456c 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Api.Models /// /// The of the installation used for new compiles. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers /// - [Permissions(ReadRight = ByondRights.ReadInstalled, WriteRight = ByondRights.ChangeVersion)] + [Permissions(ReadRight = ByondRights.ReadActive, WriteRight = ByondRights.ChangeVersion)] public Version Version { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index c8992b3575..58f208711c 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -13,24 +13,16 @@ namespace Tgstation.Server.Api.Rights /// None = 0, /// - /// User can change - /// - SetUpstreamRepository = 1, - /// - /// User can change - /// - ChangeTelemetry = 2, - /// /// User can edit themself and other s /// - EditUsers = 4, + EditUsers = 1, /// - /// User can change + /// User can gracefully restart the host /// - SoftStop = 8, + SoftStop = 2, /// /// User can change /// - ChangeVersion = 16 + ChangeVersion = 4 } } diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs index 142ea098b4..ae650b69af 100644 --- a/src/Tgstation.Server.Api/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -13,24 +13,12 @@ namespace Tgstation.Server.Api.Rights /// None = 0, /// - /// User may check the installed BYOND version + /// User may check the active installed BYOND version /// - ReadInstalled = 1, - /// - /// User may check the previous BYOND version - /// - ReadPrevious = 2, + ReadActive = 1, /// /// User may change to any BYOND version /// - ChangeVersion = 4, - /// - /// User may cancel a pending installation job - /// - Cancel = 8, - /// - /// User may read the of the installation job - /// - ReadStatus = 16, + ChangeVersion = 4 } } diff --git a/src/Tgstation.Server.Client/Components/IByondClient.cs b/src/Tgstation.Server.Client/Components/IByondClient.cs index 403f6dd34a..9f9d8421c6 100644 --- a/src/Tgstation.Server.Client/Components/IByondClient.cs +++ b/src/Tgstation.Server.Client/Components/IByondClient.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Client.Components /// /// Updates the installed BYOND /// - /// The to set to active + /// The to set to active /// The for the operation /// A representing the running operation Task SetActiveVersion(Version version, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs index 51eed66c52..e046bcf56c 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -117,8 +117,8 @@ namespace Tgstation.Server.Host.Components return new ByondExecutableLock { - DreamDaemonPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, byondInstaller.DreamDaemonName)), - DreamMakerPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, byondInstaller.DreamMakerName)), + DreamDaemonPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamDaemonName)), + DreamMakerPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamMakerName)), Version = versionToUse }; } diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index 052eff0a34..cc00821fdd 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -58,18 +58,22 @@ namespace Tgstation.Server.Host.IO return false; } } - cancellationToken.ThrowIfCancellationRequested(); - file.Seek(0, SeekOrigin.Begin); - cancellationToken.ThrowIfCancellationRequested(); - file.SetLength(data.Length); + if (data != null) + { + file.Seek(0, SeekOrigin.Begin); - cancellationToken.ThrowIfCancellationRequested(); - file.Write(data, 0, data.Length); + cancellationToken.ThrowIfCancellationRequested(); + file.SetLength(data.Length); - return true; + cancellationToken.ThrowIfCancellationRequested(); + file.Write(data, 0, data.Length); + } } + if (data == null) + File.Delete(path); + return true; } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index 37bf2f1c0f..acaf0e1f53 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -21,11 +21,6 @@ namespace Tgstation.Server.Host.Models /// const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"; - /// - /// The default git repository to pull server updates from - /// - const string DefaultUpstreamRepository = "https://github.com/tgstation/tgstation-server"; - /// /// The for the /// diff --git a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs index aa125813de..91af8fa215 100644 --- a/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs +++ b/tests/Tgstation.Server.Host.Tests/Security/TestAuthenticationContext.cs @@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Security.Tests var authContext = new AuthenticationContext(null, user, instanceUser); user.AdministrationRights = AdministrationRights.EditUsers; - instanceUser.ByondRights = ByondRights.ChangeVersion | ByondRights.ReadInstalled; + instanceUser.ByondRights = ByondRights.ChangeVersion | ByondRights.ReadActive; Assert.AreEqual((int)user.AdministrationRights, authContext.GetRight(RightsType.Administration)); Assert.AreEqual((int)instanceUser.ByondRights, authContext.GetRight(RightsType.Byond)); } From 8980f057528389eeb77986bfb8b06ae9c2ebec7b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 23 Jul 2018 23:14:53 -0400 Subject: [PATCH 308/397] Add server service to the build --- appveyor.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 98c9cb64f1..ec4f659cbe 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,6 +16,9 @@ configuration: shallow_clone: true artifacts: - path: artifacts/ServerConsole + name: Server Console + - path: artifacts/ServerService + name: Server Service cache: - ~\.nuget\packages -> **\*.csproj - C:\ProgramData\chocolatey\bin -> appveyor.yml @@ -53,6 +56,7 @@ after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% + - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/($env:CONFIGURATION)" -destination ../../artifacts/ServerService -recurse deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" From 9d2d36e10f0e0ac895a6e861b88e6ce87c560407 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 23 Jul 2018 23:16:04 -0400 Subject: [PATCH 309/397] Remove unrequired fciv --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index ec4f659cbe..62acd78bec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,7 +24,7 @@ cache: - C:\ProgramData\chocolatey\bin -> appveyor.yml - C:\ProgramData\chocolatey\lib -> appveyor.yml install: - - choco install fciv doxygen.portable codecov + - choco install doxygen.portable codecov - nuget restore tgstation-server.sln build: project: tgstation-server.sln From 489ceaf4725806c3b9a9c31ba78f370db7d4042e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 24 Jul 2018 10:12:58 -0400 Subject: [PATCH 310/397] Fuck --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 62acd78bec..f326ed3b2d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,7 +56,7 @@ after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% - - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/($env:CONFIGURATION)" -destination ../../artifacts/ServerService -recurse + - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination ../../artifacts/ServerService -recurse deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" From 5f8b1546bf4b6c045fa628a99c3553c91f561951 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 24 Jul 2018 10:49:31 -0400 Subject: [PATCH 311/397] Configuration now constantly ensures all it's directories exist --- .../Components/Configuration.cs | 20 ++++++++++++++++--- .../Components/IConfiguration.cs | 5 +++-- .../Components/Instance.cs | 4 ++-- .../Controllers/ConfigurationController.cs | 4 ---- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/Configuration.cs index 23af8efb62..41f1f34977 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/Configuration.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Components sealed class Configuration : IConfiguration { const string CodeModificationsSubdirectory = "CodeModifications"; - //const string EventScriptsSubdirectory = "EventScripts"; + const string EventScriptsSubdirectory = "EventScripts"; const string GameStaticFilesSubdirectory = "GameStaticFiles"; const string CodeModificationsHeadFile = "HeadInclude.dm"; @@ -58,9 +58,13 @@ namespace Tgstation.Server.Host.Components this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } + Task EnsureDirectories(CancellationToken cancellationToken) => Task.WhenAll(ioManager.CreateDirectory(CodeModificationsSubdirectory, cancellationToken), ioManager.CreateDirectory(EventScriptsSubdirectory, cancellationToken), ioManager.CreateDirectory(GameStaticFilesSubdirectory, cancellationToken)); + /// public async Task CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken) { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); + //just assume no other fs race conditions here var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken); var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken); @@ -101,6 +105,7 @@ namespace Tgstation.Server.Host.Components /// public async Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); var path = ValidateConfigRelativePath(configurationRelativePath); List result = new List(); @@ -132,6 +137,7 @@ namespace Tgstation.Server.Host.Components /// public async Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken) { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); var path = ValidateConfigRelativePath(configurationRelativePath); ConfigurationFile result = null; @@ -176,8 +182,9 @@ namespace Tgstation.Server.Host.Components } /// - public Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken) + public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken) { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); async Task SymlinkBase(bool files) { Task> task; @@ -189,12 +196,13 @@ namespace Tgstation.Server.Host.Components await Task.WhenAll(entries.Select(x => symlinkFactory.CreateSymbolicLink(ioManager.ResolvePath(x), ioManager.ConcatPath(destination, x), cancellationToken))).ConfigureAwait(false); } - return Task.WhenAll(SymlinkBase(true), SymlinkBase(false)); + await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false); } /// public async Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, string previousHash, CancellationToken cancellationToken) { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); var path = ValidateConfigRelativePath(configurationRelativePath); ConfigurationFile result = null; @@ -239,5 +247,11 @@ namespace Tgstation.Server.Host.Components return result; } + + /// + public Task StartAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken); + + /// + public Task StopAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index f1734e028d..e972f8e508 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using Microsoft.Extensions.Hosting; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -9,7 +10,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing the Configuration directory /// - public interface IConfiguration + public interface IConfiguration : IHostedService { /// /// Copies all files in the CodeModifications directory to diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index fea1f1b57f..8c4b30f05c 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -125,10 +125,10 @@ namespace Tgstation.Server.Host.Components } /// - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), ByondManager.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken)); + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken)); /// - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken)); /// public async Task SetAutoUpdateInterval(int? newInterval) diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index e6a3b4d81c..fb3abd7ba4 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -65,10 +65,6 @@ namespace Tgstation.Server.Host.Controllers return Json(newFile); } - catch (InvalidOperationException) - { - return BadRequest(new { message = "Attempted to delete required folder!" }); - } catch(NotImplementedException e) { return StatusCode((int)HttpStatusCode.NotImplemented, new { message = e.Message }); From dee4c3f9bb917148c61d931d8eff624891e8b592 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 24 Jul 2018 10:53:47 -0400 Subject: [PATCH 312/397] ..'s uneccessary --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index f326ed3b2d..304cf440a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,7 +56,7 @@ after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% - - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination ../../artifacts/ServerService -recurse + - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" From d1b0a69340116967ee22160037e4eaa6e43407f7 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Jul 2018 11:48:28 -0400 Subject: [PATCH 313/397] Add system execution files to console project --- build/Dockerfile | 2 +- .../Tgstation.Server.Host.Console.csproj | 9 +++++++++ src/Tgstation.Server.Host.Console/tgs.bat | 2 ++ src/Tgstation.Server.Host.Console/tgs.sh | 4 ++++ 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Host.Console/tgs.bat create mode 100755 src/Tgstation.Server.Host.Console/tgs.sh diff --git a/build/Dockerfile b/build/Dockerfile index 2b1cb8c47d..dd8d097837 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -46,4 +46,4 @@ COPY --from=publish /app . RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json VOLUME ["/config_data"] -ENTRYPOINT ["dotnet", "Tgstation.Server.Host.Console.dll"] +ENTRYPOINT ["./tgs.sh"] diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index 5bd446923d..c88d318c9c 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -21,4 +21,13 @@ + + + PreserveNewest + + + PreserveNewest + + + diff --git a/src/Tgstation.Server.Host.Console/tgs.bat b/src/Tgstation.Server.Host.Console/tgs.bat new file mode 100644 index 0000000000..a54925985c --- /dev/null +++ b/src/Tgstation.Server.Host.Console/tgs.bat @@ -0,0 +1,2 @@ +@echo off +dotnet %~dp0Tgstation.Server.Host.Console.dll diff --git a/src/Tgstation.Server.Host.Console/tgs.sh b/src/Tgstation.Server.Host.Console/tgs.sh new file mode 100755 index 0000000000..1f50a92ca5 --- /dev/null +++ b/src/Tgstation.Server.Host.Console/tgs.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +script_full_path=$(dirname "$0") +exec dotnet "$script_full_path/Tgstation.Server.Host.Console.dll" From 9d8a6bbfbf61fa26f97483b45f93f38f4021ee53 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Jul 2018 12:00:27 -0400 Subject: [PATCH 314/397] Add docker tgs.sh. Fix docker not copying appsettings jsons --- build/Dockerfile | 4 ++-- build/tgs.docker.sh | 5 +++++ src/Tgstation.Server.Host.Console/tgs.sh | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100755 build/tgs.docker.sh diff --git a/build/Dockerfile b/build/Dockerfile index dd8d097837..e4d8f487e7 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -33,7 +33,6 @@ RUN dotnet test -c Release WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests RUN dotnet test -c Release -FROM build AS publish RUN dotnet publish -c Release -o /app FROM microsoft/dotnet:2.0-runtime @@ -41,7 +40,8 @@ EXPOSE 80 WORKDIR /app -COPY --from=publish /app . +COPY --from=build /app . +COPY --from=build /src/build/tgs.docker.sh tgs.sh RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json VOLUME ["/config_data"] diff --git a/build/tgs.docker.sh b/build/tgs.docker.sh new file mode 100755 index 0000000000..ac9a5d2151 --- /dev/null +++ b/build/tgs.docker.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +cp -r /config_data/* ./ + +exec dotnet Tgstation.Server.Host.Console.dll diff --git a/src/Tgstation.Server.Host.Console/tgs.sh b/src/Tgstation.Server.Host.Console/tgs.sh index 1f50a92ca5..895becc72e 100755 --- a/src/Tgstation.Server.Host.Console/tgs.sh +++ b/src/Tgstation.Server.Host.Console/tgs.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh script_full_path=$(dirname "$0") exec dotnet "$script_full_path/Tgstation.Server.Host.Console.dll" From 389574c2cf35f9bda9be1e3df425890a243aeb3a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Jul 2018 12:01:24 -0400 Subject: [PATCH 315/397] Fix build sh having wrong path in docker.sh --- tgstation-server.sln | 1 + 1 file changed, 1 insertion(+) diff --git a/tgstation-server.sln b/tgstation-server.sln index c4739f3499..a123a25c5c 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -23,6 +23,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6 build\Dockerfile = build\Dockerfile build\Doxyfile = build\Doxyfile build\install_byond.sh = build\install_byond.sh + build\tgs.docker.sh = build\tgs.docker.sh build\tgs.ico = build\tgs.ico build\UploadCoverage.ps1 = build\UploadCoverage.ps1 EndProjectSection From 2c2680a95f1983833db74da8759763bcacdf65f1 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Jul 2018 12:50:43 -0400 Subject: [PATCH 316/397] Various cleanups very WIP --- appveyor.yml | 4 ++ .../Tgstation.Server.Api.csproj | 14 ++++++ .../Tgstation.Server.Client.csproj | 4 +- src/Tgstation.Server.Host.Startup/IServer.cs | 6 +-- .../Tgstation.Server.Host.Startup.csproj | 2 + .../Watchdog.cs | 5 +++ .../Components/IRepository.cs | 1 - .../Components/Repository.cs | 2 +- src/Tgstation.Server.Host/Server.cs | 44 ++++++++++++++++--- .../Tgstation.Server.Host.csproj | 1 + tgstation-server.sln | 1 + v4_prototype_TODO.txt | 18 ++++++++ 12 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 v4_prototype_TODO.txt diff --git a/appveyor.yml b/appveyor.yml index 304cf440a7..dc70224a83 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,6 +19,8 @@ artifacts: name: Server Console - path: artifacts/ServerService name: Server Service + - path: artifacts/ServerHost + name: Server Auto Update cache: - ~\.nuget\packages -> **\*.csproj - C:\ProgramData\chocolatey\bin -> appveyor.yml @@ -55,8 +57,10 @@ test_script: after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 + - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION% - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse + - ps: $env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/artifacts/ServerHost/Tgstation.Server.Host.dll").FileVersion deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 3da598cb1b..1ccca2d0fb 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -3,6 +3,20 @@ netstandard2.0 Full + true + Cyberboss + /tg/station + API definitions for tgstation-server + https://github.com/tgstation/tgstation-server/blob/master/LICENSE.md + https://tgstation.github.io/tgstation-server + https://raw.githubusercontent.com/tgstation/tgstation-server/master/tgs.ico + Git + https://github.com/tgstation/tgstation-server + 2018 + 4.0.0.0 + 4.0.0.0 + json web api tgstation-server tgstation ss13 byond + Prototype release diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index ca8a985c9a..141da8fae0 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -4,7 +4,7 @@ netstandard2.0 Full 4.0.0.0 - false + true Cyberboss /tg/station 13 @@ -15,6 +15,8 @@ en-CA https://raw.githubusercontent.com/tgstation/tgstation-server/master/build/tgs.ico https://github.com/tgstation/tgstation-server/blob/master/LICENSE + json web api tgstation-server tgstation ss13 byond client + Prototype release diff --git a/src/Tgstation.Server.Host.Startup/IServer.cs b/src/Tgstation.Server.Host.Startup/IServer.cs index 299e193036..1fd97d4930 100644 --- a/src/Tgstation.Server.Host.Startup/IServer.cs +++ b/src/Tgstation.Server.Host.Startup/IServer.cs @@ -7,18 +7,18 @@ namespace Tgstation.Server.Host.Startup /// /// Represents the host /// - public interface IServer + public interface IServer : IDisposable { /// /// The path to the updated assembly to run if any. Populated once returns /// - string UpdatePath { get; } + Guid UpdateGuid { get; } /// /// Runs the /// /// The for the operation /// A representing the running operation - Task RunAsync(CancellationToken cancellationToken); + Task RunAsync(string updatesPath, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj index e9797da445..13866885f6 100644 --- a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj +++ b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj @@ -3,6 +3,8 @@ netstandard2.0 Full + 4.0.0.0 + 4.0.0.0 diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index e4c3fc6a74..23f4a8968d 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -57,6 +57,8 @@ namespace Tgstation.Server.Host.Watchdog logger.LogTrace("Determining location of host assembly..."); var assemblyPath = serverFactory.GetType().Assembly.Location; logger.LogDebug("Path to initial host assembly: {0}", assemblyPath); + + var firstIteration = true; do using (logger.BeginScope("Host invocation")) { @@ -69,11 +71,14 @@ namespace Tgstation.Server.Host.Watchdog break; logger.LogInformation("Update path is set to \"{0}\", attempting host assembly hotswap...", server.UpdatePath); + GC.Collect(Int32.MaxValue, GCCollectionMode.Forced, true, true); + logger.LogTrace("Deleting old host assembly"); activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath); logger.LogTrace("Moving new host assembly in place..."); File.Move(server.UpdatePath, assemblyPath); logger.LogTrace("Atttempting to create new server factory..."); serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath); + firstIteration = false; } while (!cancellationToken.IsCancellationRequested); } diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/IRepository.cs index 09acbb72c5..08a500cb02 100644 --- a/src/Tgstation.Server.Host/Components/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/IRepository.cs @@ -1,7 +1,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { diff --git a/src/Tgstation.Server.Host/Components/Repository.cs b/src/Tgstation.Server.Host/Components/Repository.cs index ed5f2676d3..218b21932c 100644 --- a/src/Tgstation.Server.Host/Components/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository.cs @@ -55,7 +55,7 @@ namespace Tgstation.Server.Host.Components this.repository = repository ?? throw new ArgumentNullException(nameof(repository)); this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); - IsGitHubRepository = Origin.ToUpperInvariant().Contains("GITHUB.COM"); + IsGitHubRepository = Origin.ToUpperInvariant().Contains("://GITHUB.COM/"); } /// diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index ddf5ec2ece..5516c3287f 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host @@ -13,13 +14,23 @@ namespace Tgstation.Server.Host sealed class Server : IServer, IServerUpdater { /// - public string UpdatePath { get; private set; } + public Guid? UpdateGuid { get; private set; } /// /// The for the /// readonly IWebHostBuilder webHostBuilder; + /// + /// The for the + /// + readonly SemaphoreSlim semaphore; + + /// + /// The absolute path to install updates to + /// + readonly string updatePath; + /// /// The for the /// @@ -29,7 +40,17 @@ namespace Tgstation.Server.Host /// Construct a /// /// The value of - public Server(IWebHostBuilder webHostBuilder) => this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder)); + /// The value of + public Server(IWebHostBuilder webHostBuilder, string updatePath) + { + this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder)); + this.updatePath = updatePath ?? throw new ArgumentNullException(nameof(updatePath)); + + semaphore = new SemaphoreSlim(1); + } + + /// + public void Dispose() => semaphore.Dispose(); /// [ExcludeFromCodeCoverage] @@ -45,15 +66,24 @@ namespace Tgstation.Server.Host } /// - public void ApplyUpdate(string updatePath) + public async Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) { - lock (this) + using(await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { - if (updatePath != null) + if (UpdateGuid != null) throw new InvalidOperationException("ApplyUpdate has already been called!"); - UpdatePath = updatePath; + UpdateGuid = Guid.NewGuid(); + try + { + await ioManager.WriteAllBytes(serverUpdatePath, updateData, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + UpdateGuid = null; + throw; + } + cancellationTokenSource.Cancel(); } - cancellationTokenSource.Cancel(); } /// diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 662d012233..5b23cc7789 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -48,6 +48,7 @@ + diff --git a/tgstation-server.sln b/tgstation-server.sln index a123a25c5c..6b4c152ebc 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution appveyor.yml = appveyor.yml LICENSE = LICENSE README.md = README.md + v4_prototype_TODO.txt = v4_prototype_TODO.txt EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Console", "src\Tgstation.Server.Host.Console\Tgstation.Server.Host.Console.csproj", "{A09C947F-4B9B-4AEE-AB50-47055EA05F18}" diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt new file mode 100644 index 0000000000..f1e606e7b5 --- /dev/null +++ b/v4_prototype_TODO.txt @@ -0,0 +1,18 @@ +Watchdog performance counters + - Remember to catch PlatformNotSupportedException() + +Event scripts + +Repo synchronization + +Repo controller + +Install as network service instead of LocalSystem if win10 >= Creators update to allow for unprivileged symlinks + +Nuspec stuff for api and client + +Verify the byond cache folder location on linux + +Map hosting address and certificate directly from configuration + +Server updates. Use FOLDERS so dependencies can be properly packaged From d2d63e235b91bcf280c27e139985c66e0bce1530 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Jul 2018 15:25:42 -0400 Subject: [PATCH 317/397] Create a truly async ZipToDirectory --- src/Tgstation.Server.Host.Startup/IServer.cs | 4 +-- .../IServerFactory.cs | 3 +- .../Components/ByondManager.cs | 7 ++-- .../Core/IServerUpdater.cs | 10 ++++-- .../IO/DefaultIOManager.cs | 36 ++++++++++++++++++- src/Tgstation.Server.Host/IO/IIOManager.cs | 9 +++++ src/Tgstation.Server.Host/Server.cs | 4 +-- 7 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/Tgstation.Server.Host.Startup/IServer.cs b/src/Tgstation.Server.Host.Startup/IServer.cs index 1fd97d4930..3ab78291f5 100644 --- a/src/Tgstation.Server.Host.Startup/IServer.cs +++ b/src/Tgstation.Server.Host.Startup/IServer.cs @@ -12,13 +12,13 @@ namespace Tgstation.Server.Host.Startup /// /// The path to the updated assembly to run if any. Populated once returns /// - Guid UpdateGuid { get; } + Guid? UpdateGuid { get; } /// /// Runs the /// /// The for the operation /// A representing the running operation - Task RunAsync(string updatesPath, CancellationToken cancellationToken); + Task RunAsync(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host.Startup/IServerFactory.cs b/src/Tgstation.Server.Host.Startup/IServerFactory.cs index ed92399240..a787e083f8 100644 --- a/src/Tgstation.Server.Host.Startup/IServerFactory.cs +++ b/src/Tgstation.Server.Host.Startup/IServerFactory.cs @@ -9,7 +9,8 @@ /// Create a /// /// The arguments for the + /// The directory in which to install server updates /// A new - IServer CreateServer(string[] args); + IServer CreateServer(string[] args, string updatePath); } } diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs index e046bcf56c..4b85832126 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -86,12 +86,9 @@ namespace Tgstation.Server.Host.Components await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false); await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false); - var resolvedPath = ioManager.ResolvePath(versionKey); - using (var zipBytes = new MemoryStream(await downloadTask.ConfigureAwait(false))) - using (var archive = new ZipArchive(zipBytes)) - await Task.Factory.StartNew(() => archive.ExtractToDirectory(resolvedPath), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + await ioManager.ZipToDirectory(versionKey, await downloadTask.ConfigureAwait(false), cancellationToken).ConfigureAwait(false); - await byondInstaller.InstallByond(resolvedPath, version, 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 await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Core/IServerUpdater.cs b/src/Tgstation.Server.Host/Core/IServerUpdater.cs index c4b43eb1b2..ed5a4e76f0 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdater.cs +++ b/src/Tgstation.Server.Host/Core/IServerUpdater.cs @@ -1,4 +1,7 @@ using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Core { @@ -10,8 +13,11 @@ namespace Tgstation.Server.Host.Core /// /// Run a new assembly and stop the current one. This will likely trigger all active s /// - /// The path to the new assembly - void ApplyUpdate(string updatePath); + /// The s of the .zip file that contains the new assembly + /// The for the operation + /// The for the operation + /// A representing the running operation + Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken); /// /// Register a given to run before stopping the server for updates diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 793b5881e2..b24653f8fe 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using System.Net; using System.Text; @@ -46,6 +47,12 @@ namespace Tgstation.Server.Host.IO dir.Delete(true); } + /// + /// Opens a for async writing at a given + /// + /// The path to open the at + /// A new ready for async writing + static FileStream OpenWriteStream(string path) => new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, DefaultBufferSize, true); /// /// Copies a directory from to @@ -205,7 +212,7 @@ namespace Tgstation.Server.Host.IO public async Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken) { path = ResolvePath(path); - using (var file = File.Open(path, FileMode.Create, FileAccess.Write)) + using (var file = OpenWriteStream(path)) await file.WriteAsync(contents, 0, contents.Length, cancellationToken).ConfigureAwait(false); } @@ -252,5 +259,32 @@ namespace Tgstation.Server.Host.IO using (cancellationToken.Register(() => wc.CancelAsync())) return await wc.DownloadDataTaskAsync(url).ConfigureAwait(false); } + + /// + public async Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken) + { + path = ResolvePath(path); + if (zipFileBytes == null) + throw new ArgumentNullException(nameof(zipFileBytes)); + + using (var ms = new MemoryStream(zipFileBytes)) + { + zipFileBytes = null; + using (var archive = new ZipArchive(ms)) + { + string GetEntryName(ZipArchiveEntry entry) => ConcatPath(path, GetDirectoryName(entry.FullName)); + //create directories first + await Task.WhenAll(archive.Entries.Where(x => x.Name.Length == 0).Select(x => CreateDirectory(GetEntryName(x), cancellationToken))).ConfigureAwait(false); + //extract files + await Task.WhenAll(archive.Entries.Where(x => x.Name.Length > 0).Select(async x => + { + var entryPath = GetEntryName(x); + using (var stream = x.Open()) + using (var file = OpenWriteStream(entryPath)) + await stream.CopyToAsync(file).ConfigureAwait(false); + })).ConfigureAwait(false); + } + } + } } } diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index ad320805d3..3654fed7f5 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -162,5 +162,14 @@ namespace Tgstation.Server.Host.IO /// A for the operation /// A resulting in the s of the downloaded file Task DownloadFile(Uri url, CancellationToken cancellationToken); + + /// + /// Extract a set of to a given + /// + /// The path to unzip to + /// The s of the + /// The for the operation + /// A representing the running operation + Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 5516c3287f..42f9cafeae 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -75,9 +75,9 @@ namespace Tgstation.Server.Host UpdateGuid = Guid.NewGuid(); try { - await ioManager.WriteAllBytes(serverUpdatePath, updateData, cancellationToken).ConfigureAwait(false); + await ioManager.ZipToDirectory(updatePath, updateZipData, cancellationToken).ConfigureAwait(false); } - catch (OperationCanceledException) + catch { UpdateGuid = null; throw; From d18c584f63e109f7206b7ebcbf7f0a5296b6e637 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Jul 2018 15:46:56 -0400 Subject: [PATCH 318/397] Fix things --- .../Tgstation.Server.Api.csproj | 4 +-- .../IsolatedServerFactory.cs | 7 ++-- .../Watchdog.cs | 33 ++++++++++++------- .../IO/DefaultIOManager.cs | 2 +- src/Tgstation.Server.Host/ServerFactory.cs | 2 +- .../Core/TestApplication.cs | 4 ++- .../TestIsolatedServerFactory.cs | 2 +- .../TestWatchdog.cs | 31 +---------------- 8 files changed, 34 insertions(+), 51 deletions(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 1ccca2d0fb..96b02fd263 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -33,9 +33,9 @@ - + - + diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs index 2545747721..b51de730e9 100644 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs @@ -25,9 +25,10 @@ namespace Tgstation.Server.Host.Watchdog /// /// Loads the at and creates an from it /// - /// The arguments for the + /// The arguments for the + /// The updatePath for the /// A new - public IServer CreateServer(string[] args) + public IServer CreateServer(string[] args, string updatePath) { var assembly = LoadFromAssemblyPath(assemblyPath); //find the IServerFactory implementation @@ -36,7 +37,7 @@ namespace Tgstation.Server.Host.Watchdog var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); - return serverFactory.CreateServer(args); + return serverFactory.CreateServer(args, updatePath); } //honestly have no idea what this is for, but the examples i see just return null and it seems to work just fine diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 23f4a8968d..07372198e5 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -57,27 +57,36 @@ namespace Tgstation.Server.Host.Watchdog logger.LogTrace("Determining location of host assembly..."); var assemblyPath = serverFactory.GetType().Assembly.Location; logger.LogDebug("Path to initial host assembly: {0}", assemblyPath); + var assemblyName = Path.GetFileName(assemblyPath); + const string UpdatePath = "Updates"; + var newAssemblyDirectory = Path.Combine(Path.GetDirectoryName(assemblyPath), UpdatePath); var firstIteration = true; do using (logger.BeginScope("Host invocation")) { - var server = serverFactory.CreateServer(args); - logger.LogTrace("Running server..."); - await server.RunAsync(cancellationToken).ConfigureAwait(false); - logger.LogInformation("Active host exited."); + Guid updateGuid; + using (var server = serverFactory.CreateServer(args, newAssemblyDirectory)) + { + logger.LogTrace("Running server..."); + await server.RunAsync(cancellationToken).ConfigureAwait(false); + logger.LogInformation("Active host exited."); - if (server.UpdatePath == null) - break; + if (!server.UpdateGuid.HasValue) + break; + updateGuid = server.UpdateGuid.Value; + } - logger.LogInformation("Update path is set to \"{0}\", attempting host assembly hotswap...", server.UpdatePath); + logger.LogInformation("Update path is set to \"{0}\", attempting host assembly hotswap...", updateGuid); GC.Collect(Int32.MaxValue, GCCollectionMode.Forced, true, true); - logger.LogTrace("Deleting old host assembly"); - activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath); - logger.LogTrace("Moving new host assembly in place..."); - File.Move(server.UpdatePath, assemblyPath); + if (!firstIteration) + { + logger.LogTrace("Deleting old host assembly"); + //TODO: make this use directories + //activeAssemblyDeleter.DeleteActiveAssembly(newAssemblyDirectory); + } logger.LogTrace("Atttempting to create new server factory..."); - serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath); + serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(Path.Combine(newAssemblyDirectory, updateGuid.ToString(), assemblyName)); firstIteration = false; } while (!cancellationToken.IsCancellationRequested); diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index b24653f8fe..a5f2b122c7 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -272,7 +272,7 @@ namespace Tgstation.Server.Host.IO zipFileBytes = null; using (var archive = new ZipArchive(ms)) { - string GetEntryName(ZipArchiveEntry entry) => ConcatPath(path, GetDirectoryName(entry.FullName)); + string GetEntryName(ZipArchiveEntry entry) => ConcatPath(path, entry.FullName); //create directories first await Task.WhenAll(archive.Entries.Where(x => x.Name.Length == 0).Select(x => CreateDirectory(GetEntryName(x), cancellationToken))).ConfigureAwait(false); //extract files diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index 810ef924e2..ffe440055d 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -7,6 +7,6 @@ namespace Tgstation.Server.Host public sealed class ServerFactory : IServerFactory { /// - public IServer CreateServer(string[] args) => new Server(WebHost.CreateDefaultBuilder(args)); + public IServer CreateServer(string[] args, string updatePath) => new Server(WebHost.CreateDefaultBuilder(args), updatePath); } } diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 22833bacdb..6a8eb5d68a 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -4,14 +4,16 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; +using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Core.Tests { [TestClass] public sealed class TestApplication : IServerUpdater { - public void ApplyUpdate(string updatePath) => throw new NotImplementedException(); + public Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException(); public void RegisterForUpdate(Action action) => throw new NotImplementedException(); diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs index ab382bff97..384139308b 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs @@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Watchdog.Tests public void TestLoading() { var isf = new IsolatedServerFactory(typeof(ServerFactory).Assembly.Location); - Assert.IsNotNull(isf.CreateServer(Array.Empty())); + Assert.IsNotNull(isf.CreateServer(Array.Empty(), String.Empty)); } } } diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs index 56ec75351a..8f233120d5 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs @@ -29,7 +29,7 @@ namespace Tgstation.Server.Host.Watchdog.Tests { readonly IServer server; public MockServerFactory(IServer server) => this.server = server; - public IServer CreateServer(string[] args) => server; + public IServer CreateServer(string[] args, string updatePath) => server; } [TestMethod] @@ -50,34 +50,5 @@ namespace Tgstation.Server.Host.Watchdog.Tests mockServer.VerifyAll(); } } - - [TestMethod] - public async Task TestRunAsyncWithUpdate() - { - var mockServer = new Mock(); - mockServer.Setup(x => x.UpdatePath).Returns(GetType().Assembly.Location).Verifiable(); - var mockServerFactory = new MockServerFactory(mockServer.Object); - var mockActiveAssemblyDeleter = new Mock(); - var mockIsolatedServerContextFactory = new Mock(); - mockIsolatedServerContextFactory.Setup(x => x.CreateIsolatedServerFactory(GetType().Assembly.Location)).Returns(mockServerFactory).Verifiable(); - var mockLogger = new LoggerFactory().CreateLogger(); - - var wd = new Watchdog(mockServerFactory, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); - - using (var cts = new CancellationTokenSource()) - { - int count = 0; - mockServer.Setup(x => x.RunAsync(cts.Token)).Callback(() => - { - if (++count > 1) - cts.Cancel(); - }).Returns(Task.CompletedTask).Verifiable(); - await wd.RunAsync(Array.Empty(), cts.Token).ConfigureAwait(false); - - mockServer.VerifyAll(); - mockActiveAssemblyDeleter.Verify(x => x.DeleteActiveAssembly(GetType().Assembly.Location), Times.Exactly(2)); - mockIsolatedServerContextFactory.VerifyAll(); - } - } } } From 10955fc3c8a7e33e165de4a5a8742a5dd341569e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Jul 2018 17:12:55 -0400 Subject: [PATCH 319/397] Time to go home --- .../Tgstation.Server.Host.Service.csproj | 1 + .../Tgstation.Server.Host.Startup.csproj | 4 + .../IActiveAssemblyDeleter.cs | 14 ---- .../IActiveLibraryDeleter.cs | 14 ++++ .../IsolatedServerFactory.cs | 26 +++++-- .../PosixActiveAssemblyDeleter.cs | 15 ---- .../PosixActiveLibraryDeleter.cs | 14 ++++ .../Tgstation.Server.Host.Watchdog.csproj | 1 - .../Watchdog.cs | 77 +++++++++---------- .../WatchdogFactory.cs | 2 +- .../WindowsActiveAssemblyDeleter.cs | 37 --------- .../WindowsActiveLibraryDeleter.cs | 36 +++++++++ src/Tgstation.Server.Host/Server.cs | 1 + .../TestIsolatedAssemblyContextFactory.cs | 18 ----- .../TestIsolatedServerFactory.cs | 26 ------- .../TestPosixActiveAssemblyDeleter.cs | 40 ---------- .../TestWatchdog.cs | 16 ++-- .../TestWindowsActiveAssemblyDeleter.cs | 48 ------------ tgstation-server.sln | 3 + 19 files changed, 137 insertions(+), 256 deletions(-) delete mode 100644 src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/IActiveLibraryDeleter.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/PosixActiveLibraryDeleter.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs create mode 100644 src/Tgstation.Server.Host.Watchdog/WindowsActiveLibraryDeleter.cs delete mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedAssemblyContextFactory.cs delete mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs delete mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestPosixActiveAssemblyDeleter.cs delete mode 100644 tests/Tgstation.Server.Host.Watchdog.Tests/TestWindowsActiveAssemblyDeleter.cs diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index ed7a2783e3..54ee9a72db 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -80,6 +80,7 @@ + diff --git a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj index 13866885f6..126b5970dd 100644 --- a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj +++ b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj @@ -17,4 +17,8 @@ latest + + + + diff --git a/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs deleted file mode 100644 index 70c5762154..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/IActiveAssemblyDeleter.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// For deleting s used by the program - /// - interface IActiveAssemblyDeleter - { - /// - /// Deletes an that is in use by the runtime - /// - /// The of the to delete - void DeleteActiveAssembly(string assemblyPath); - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IActiveLibraryDeleter.cs b/src/Tgstation.Server.Host.Watchdog/IActiveLibraryDeleter.cs new file mode 100644 index 0000000000..34cbef1010 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/IActiveLibraryDeleter.cs @@ -0,0 +1,14 @@ +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// For deleting libraries used by the program + /// + interface IActiveLibraryDeleter + { + /// + /// Deletes a library that is in use by the runtime + /// + /// The path of the library to delete + void DeleteActiveLibrary(string assemblyPath); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs index b51de730e9..0b83b829be 100644 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Loader; @@ -30,14 +32,26 @@ namespace Tgstation.Server.Host.Watchdog /// A new public IServer CreateServer(string[] args, string updatePath) { - var assembly = LoadFromAssemblyPath(assemblyPath); - //find the IServerFactory implementation + //help here: https://stackoverflow.com/questions/40908568/assembly-loading-in-net-core - var serverFactoryInterfaceType = typeof(IServerFactory); - var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); + var oldCd = Environment.CurrentDirectory; + Directory.SetCurrentDirectory(Path.GetDirectoryName(assemblyPath)); + try + { + var assembly = LoadFromAssemblyPath(assemblyPath); - var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); - return serverFactory.CreateServer(args, updatePath); + //find the IServerFactory implementation + var serverFactoryInterfaceType = typeof(IServerFactory); + var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); + + var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); + + return serverFactory.CreateServer(args, updatePath); + } + finally + { + Directory.SetCurrentDirectory(oldCd); + } } //honestly have no idea what this is for, but the examples i see just return null and it seems to work just fine diff --git a/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs deleted file mode 100644 index e626ecddf4..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/PosixActiveAssemblyDeleter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.IO; -using System.Reflection; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// See for POSIX systems - /// - sealed class PosixActiveAssemblyDeleter : IActiveAssemblyDeleter - { - /// - public void DeleteActiveAssembly(string assemblyPath) => File.Delete(assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath))); //glory of inodes - } -} diff --git a/src/Tgstation.Server.Host.Watchdog/PosixActiveLibraryDeleter.cs b/src/Tgstation.Server.Host.Watchdog/PosixActiveLibraryDeleter.cs new file mode 100644 index 0000000000..c07b80572d --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/PosixActiveLibraryDeleter.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// See for POSIX systems + /// + sealed class PosixActiveLibraryDeleter : IActiveLibraryDeleter + { + /// + public void DeleteActiveLibrary(string assemblyPath) => Directory.Delete(assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath)), true); //glory of inodes + } +} diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index 70e1a609ed..0b09864c0e 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -23,7 +23,6 @@ - diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 07372198e5..e1bad698f4 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using System; using System.IO; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Startup; @@ -10,15 +11,11 @@ namespace Tgstation.Server.Host.Watchdog /// sealed class Watchdog : IWatchdog { - /// - /// The initial for the - /// - readonly IServerFactory initialServerFactory; /// - /// The for the + /// The for the /// - readonly IActiveAssemblyDeleter activeAssemblyDeleter; + readonly IActiveLibraryDeleter activeLibraryDeleter; /// /// The for the @@ -33,14 +30,12 @@ namespace Tgstation.Server.Host.Watchdog /// /// Construct a /// - /// The value of - /// The value of + /// The value of /// The value of /// The value of - public Watchdog(IServerFactory initialServerFactory, IActiveAssemblyDeleter activeAssemblyDeleter, IIsolatedAssemblyContextFactory isolatedAssemblyLoader, ILogger logger) + public Watchdog(IActiveLibraryDeleter activeLibraryDeleter, IIsolatedAssemblyContextFactory isolatedAssemblyLoader, ILogger logger) { - this.initialServerFactory = initialServerFactory ?? throw new ArgumentNullException(nameof(initialServerFactory)); - this.activeAssemblyDeleter = activeAssemblyDeleter ?? throw new ArgumentNullException(nameof(activeAssemblyDeleter)); + this.activeLibraryDeleter = activeLibraryDeleter ?? throw new ArgumentNullException(nameof(activeLibraryDeleter)); this.isolatedAssemblyLoader = isolatedAssemblyLoader ?? throw new ArgumentNullException(nameof(isolatedAssemblyLoader)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -48,48 +43,43 @@ namespace Tgstation.Server.Host.Watchdog /// public async Task RunAsync(string[] args, CancellationToken cancellationToken) { + const string DefaultAssemblyPath = "Default"; + + var assemblyStoragePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "lib"); + var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); + logger.LogInformation("Host watchdog starting..."); + + var nextAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, DefaultAssemblyPath)); + string lastAssemblyPath = null; try { - //first run the host we started with - logger.LogTrace("Running with initial server factory..."); - var serverFactory = initialServerFactory; - logger.LogTrace("Determining location of host assembly..."); - var assemblyPath = serverFactory.GetType().Assembly.Location; - logger.LogDebug("Path to initial host assembly: {0}", assemblyPath); - var assemblyName = Path.GetFileName(assemblyPath); - const string UpdatePath = "Updates"; - var newAssemblyDirectory = Path.Combine(Path.GetDirectoryName(assemblyPath), UpdatePath); - - var firstIteration = true; - do + while (!cancellationToken.IsCancellationRequested) using (logger.BeginScope("Host invocation")) { + logger.LogTrace("Atttempting to create new server factory..."); Guid updateGuid; - using (var server = serverFactory.CreateServer(args, newAssemblyDirectory)) - { - logger.LogTrace("Running server..."); - await server.RunAsync(cancellationToken).ConfigureAwait(false); - logger.LogInformation("Active host exited."); + { //forces serverFactory out of the picture once the scope ends + var serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(Path.Combine(nextAssemblyPath, assemblyName)); + using (var server = serverFactory.CreateServer(args, assemblyStoragePath)) + { + logger.LogTrace("Running server..."); + await server.RunAsync(cancellationToken).ConfigureAwait(false); + logger.LogInformation("Active host exited."); - if (!server.UpdateGuid.HasValue) - break; - updateGuid = server.UpdateGuid.Value; + if (!server.UpdateGuid.HasValue) + break; + updateGuid = server.UpdateGuid.Value; + } } logger.LogInformation("Update path is set to \"{0}\", attempting host assembly hotswap...", updateGuid); GC.Collect(Int32.MaxValue, GCCollectionMode.Forced, true, true); - if (!firstIteration) - { - logger.LogTrace("Deleting old host assembly"); - //TODO: make this use directories - //activeAssemblyDeleter.DeleteActiveAssembly(newAssemblyDirectory); - } - logger.LogTrace("Atttempting to create new server factory..."); - serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(Path.Combine(newAssemblyDirectory, updateGuid.ToString(), assemblyName)); - firstIteration = false; + + activeLibraryDeleter.DeleteActiveLibrary(nextAssemblyPath); + + nextAssemblyPath = Path.Combine(assemblyStoragePath, updateGuid.ToString()); } - while (!cancellationToken.IsCancellationRequested); } catch (OperationCanceledException) { @@ -98,6 +88,11 @@ namespace Tgstation.Server.Host.Watchdog catch (Exception e) { logger.LogCritical("Error running host assembly! Exception: {0}", e); + nextAssemblyPath = lastAssemblyPath ?? DefaultAssemblyPath; //don't wanna save a critfailed assembly + } + if (nextAssemblyPath != DefaultAssemblyPath) + { + logger.LogInformation("Setting next default host assembly path to {0}...", nextAssemblyPath); } logger.LogInformation("Host watchdog exiting..."); } diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs index 8e2ac94dce..f9c11a511c 100644 --- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -10,6 +10,6 @@ namespace Tgstation.Server.Host.Watchdog { /// [ExcludeFromCodeCoverage] - public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory(), loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory))); + public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveLibraryDeleter)new WindowsActiveLibraryDeleter() : new PosixActiveLibraryDeleter(), new IsolatedAssemblyContextFactory(), loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory))); } } diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs deleted file mode 100644 index a926cfc51d..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/WindowsActiveAssemblyDeleter.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Runtime.InteropServices; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// See for Windows systems - /// - sealed class WindowsActiveAssemblyDeleter : IActiveAssemblyDeleter - { - /// - /// Set a file located at to be deleted on reboot - /// - /// The file to delete on reboot - [ExcludeFromCodeCoverage] - static void DeleteFileOnReboot(string path) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - /// - public void DeleteActiveAssembly(string assemblyPath) - { - if (assemblyPath == null) - throw new ArgumentNullException(nameof(assemblyPath)); - - //Can't use Path.GetTempFileName() because it may cross drives, which won't actually rename the file - var tmpLocation = String.Concat(assemblyPath, Guid.NewGuid()); - File.Move(assemblyPath, tmpLocation); - DeleteFileOnReboot(tmpLocation); - } - } -} diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveLibraryDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveLibraryDeleter.cs new file mode 100644 index 0000000000..a2a98b4bac --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/WindowsActiveLibraryDeleter.cs @@ -0,0 +1,36 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// See for Windows systems + /// + sealed class WindowsActiveLibraryDeleter : IActiveLibraryDeleter + { + /// + /// Set a directory located at to be deleted on reboot + /// + /// The file to delete on reboot + [ExcludeFromCodeCoverage] + static void DeleteDirectoryOnReboot(string path) + { + if (!NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + /// + public void DeleteActiveLibrary(string assemblyPath) + { + if (assemblyPath == null) + throw new ArgumentNullException(nameof(assemblyPath)); + + var tmpLocation = Path.Combine(Path.GetDirectoryName(assemblyPath), Guid.NewGuid().ToString()); + Directory.Move(assemblyPath, tmpLocation); + DeleteDirectoryOnReboot(tmpLocation); + } + } +} diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 42f9cafeae..e01c600962 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -56,6 +56,7 @@ namespace Tgstation.Server.Host [ExcludeFromCodeCoverage] public async Task RunAsync(CancellationToken cancellationToken) { + Console.WriteLine("Hello world!"); using (cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) using (var webHost = webHostBuilder .UseStartup() diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedAssemblyContextFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedAssemblyContextFactory.cs deleted file mode 100644 index 2f2e2d0f7d..0000000000 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedAssemblyContextFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Tgstation.Server.Host.Watchdog.Tests -{ - /// - /// Tests for - /// - [TestClass] - public sealed class TestIsolatedAssemblyContextFactory - { - [TestMethod] - public void TestServerFactoryCreation() - { - var contextFactory = new IsolatedAssemblyContextFactory(); - Assert.IsNotNull(contextFactory.CreateIsolatedServerFactory(typeof(ServerFactory).Assembly.Location)); - } - } -} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs deleted file mode 100644 index 384139308b..0000000000 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestIsolatedServerFactory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; - -namespace Tgstation.Server.Host.Watchdog.Tests -{ - /// - /// Tests for - /// - [TestClass] - public sealed class TestIsolatedServerFactory - { - [TestMethod] - public void TestConstruction() - { - Assert.ThrowsException(() => new IsolatedServerFactory(null)); - var isf = new IsolatedServerFactory(typeof(ServerFactory).Assembly.Location); - } - - [TestMethod] - public void TestLoading() - { - var isf = new IsolatedServerFactory(typeof(ServerFactory).Assembly.Location); - Assert.IsNotNull(isf.CreateServer(Array.Empty(), String.Empty)); - } - } -} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestPosixActiveAssemblyDeleter.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestPosixActiveAssemblyDeleter.cs deleted file mode 100644 index 9f77307950..0000000000 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestPosixActiveAssemblyDeleter.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.IO; - -namespace Tgstation.Server.Host.Watchdog.Tests -{ - /// - /// Tests for - /// - [TestClass] - public sealed class TestPosixActiveAssemblyDeleter - { - [TestMethod] - public void TestAssemblyDeletion() - { - var ourAssembly = GetType().Assembly; - var fakeAssemblyPath = String.Concat(ourAssembly.Location, Guid.NewGuid()); - File.Copy(ourAssembly.Location, fakeAssemblyPath); - - try - { - var deleter = new PosixActiveAssemblyDeleter(); - deleter.DeleteActiveAssembly(fakeAssemblyPath); - Assert.IsFalse(File.Exists(fakeAssemblyPath)); - } - catch - { - File.Delete(fakeAssemblyPath); - throw; - } - } - - [TestMethod] - public void TestNullInvoke() - { - var deleter = new PosixActiveAssemblyDeleter(); - Assert.ThrowsException(() => deleter.DeleteActiveAssembly(null)); - } - } -} diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs index 8f233120d5..15685e68e4 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs @@ -14,15 +14,13 @@ namespace Tgstation.Server.Host.Watchdog.Tests [TestMethod] public void TestConstruction() { - Assert.ThrowsException(() => new Watchdog(null, null, null, null)); - var mockServerFactory = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, null, null, null)); - var mockActiveAssemblyDeleter = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, null, null)); + Assert.ThrowsException(() => new Watchdog(null, null, null)); + var mockActiveAssemblyDeleter = new Mock(); + Assert.ThrowsException(() => new Watchdog(mockActiveAssemblyDeleter.Object, null, null)); var mockIsolatedServerContextFactory = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, null)); + Assert.ThrowsException(() => new Watchdog(mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, null)); var mockLogger = new LoggerFactory().CreateLogger(); - var wd = new Watchdog(mockServerFactory.Object, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); + var wd = new Watchdog(mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); } class MockServerFactory : IServerFactory @@ -37,11 +35,11 @@ namespace Tgstation.Server.Host.Watchdog.Tests { var mockServer = new Mock(); var mockServerFactory = new MockServerFactory(mockServer.Object); - var mockActiveAssemblyDeleter = new Mock(); + var mockActiveAssemblyDeleter = new Mock(); var mockIsolatedServerContextFactory = new Mock(); var mockLogger = new LoggerFactory().CreateLogger(); - var wd = new Watchdog(mockServerFactory, mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); + var wd = new Watchdog(mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); using (var cts = new CancellationTokenSource()) { diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWindowsActiveAssemblyDeleter.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWindowsActiveAssemblyDeleter.cs deleted file mode 100644 index 70e0a326c9..0000000000 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWindowsActiveAssemblyDeleter.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.ComponentModel; -using System.IO; - -namespace Tgstation.Server.Host.Watchdog.Tests -{ - /// - /// Tests for - /// - [TestClass] - public sealed class TestWindowsActiveAssemblyDeleter - { - [TestMethod] - public void TestAssemblyDeletion() - { - var ourAssembly = GetType().Assembly; - var fakeAssemblyPath = String.Concat(ourAssembly.Location, Guid.NewGuid()); - File.Copy(ourAssembly.Location, fakeAssemblyPath); - - try - { - var deleter = new WindowsActiveAssemblyDeleter(); - try - { - deleter.DeleteActiveAssembly(fakeAssemblyPath); - } - catch (Win32Exception e) - { - Assert.AreEqual(e.NativeErrorCode, 5); - } - Assert.IsFalse(File.Exists(fakeAssemblyPath)); - } - catch - { - File.Delete(fakeAssemblyPath); - throw; - } - } - - [TestMethod] - public void TestNullInvoke() - { - var deleter = new WindowsActiveAssemblyDeleter(); - Assert.ThrowsException(() => deleter.DeleteActiveAssembly(null)); - } - } -} diff --git a/tgstation-server.sln b/tgstation-server.sln index 6b4c152ebc..779617bbd2 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -52,6 +52,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Tests", "tests\Tgstation.Server.Host.Tests\Tgstation.Server.Host.Tests.csproj", "{A3362FF6-550F-480F-859E-8EC1EB6EAB31}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watchdog", "src\Tgstation.Server.Host.Watchdog\Tgstation.Server.Host.Watchdog.csproj", "{5D2D682C-6BF0-439C-850B-6AB945BBEAEA}" + ProjectSection(ProjectDependencies) = postProject + {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A} = {2B69AD6D-2B5A-4023-8EAD-0BD1B18E028A} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watchdog.Tests", "tests\Tgstation.Server.Host.Watchdog.Tests\Tgstation.Server.Host.Watchdog.Tests.csproj", "{7500F776-4384-4B5F-A8D8-22461CAD108B}" EndProject From 4f59d9717cd6d4b51edd6ebb11faae6d0d621986 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 10:34:46 -0400 Subject: [PATCH 320/397] Fix up the dynamic loading stuff --- appveyor.yml | 2 + .../Tgstation.Server.Api.csproj | 2 +- .../Program.cs | 16 +++++++ ...Tgstation.Server.Host.Console.Debug.csproj | 20 ++++++++ .../Tgstation.Server.Host.Console.csproj | 4 ++ .../Tgstation.Server.Host.Startup.csproj | 4 -- .../IsolatedServerFactory.cs | 47 +++++++++++-------- .../Tgstation.Server.Host.Watchdog.csproj | 1 + .../Watchdog.cs | 3 +- src/Tgstation.Server.Host/Core/Application.cs | 2 +- ...Tgstation.Server.Host.Service.Tests.csproj | 2 + tgstation-server.sln | 8 ++++ 12 files changed, 84 insertions(+), 27 deletions(-) create mode 100644 src/Tgstation.Server.Host.Console.Debug/Program.cs create mode 100644 src/Tgstation.Server.Host.Console.Debug/Tgstation.Server.Host.Console.Debug.csproj diff --git a/appveyor.yml b/appveyor.yml index dc70224a83..b72de09803 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -60,6 +60,8 @@ after_test: - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION% - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse + - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerService/lib/Default -recurse + - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerConsole/lib/Default -recurse - ps: $env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/artifacts/ServerHost/Tgstation.Server.Host.dll").FileVersion deploy: - provider: GitHub diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 96b02fd263..c8c0b05bed 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -34,7 +34,7 @@ - + diff --git a/src/Tgstation.Server.Host.Console.Debug/Program.cs b/src/Tgstation.Server.Host.Console.Debug/Program.cs new file mode 100644 index 0000000000..231ff0654a --- /dev/null +++ b/src/Tgstation.Server.Host.Console.Debug/Program.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Tgstation.Server.Host.Startup; + +namespace Tgstation.Server.Host.Console.Debug +{ + static class Program + { + internal static IServerFactory serverFactory = new ServerFactory(); + + static async Task Main(string[] args) + { + using (var server = serverFactory.CreateServer(args, "Updates")) + await server.RunAsync(default).ConfigureAwait(false); + } + } +} diff --git a/src/Tgstation.Server.Host.Console.Debug/Tgstation.Server.Host.Console.Debug.csproj b/src/Tgstation.Server.Host.Console.Debug/Tgstation.Server.Host.Console.Debug.csproj new file mode 100644 index 0000000000..6369c0169a --- /dev/null +++ b/src/Tgstation.Server.Host.Console.Debug/Tgstation.Server.Host.Console.Debug.csproj @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp2.0 + + + + 7.1 + + + + 7.1 + + + + + + + diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index c88d318c9c..cb7add9153 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -17,6 +17,10 @@ latest + + + + diff --git a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj index 126b5970dd..13866885f6 100644 --- a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj +++ b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj @@ -17,8 +17,4 @@ latest - - - - diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs index 0b83b829be..654577329b 100644 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -13,6 +12,10 @@ namespace Tgstation.Server.Host.Watchdog /// sealed class IsolatedServerFactory : AssemblyLoadContext, IServerFactory { + const string DllExtension = "dll"; + + static readonly string assemblyFileName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), DllExtension); + /// /// The path of the to load /// @@ -22,7 +25,24 @@ namespace Tgstation.Server.Host.Watchdog /// Construct a /// /// The value of - public IsolatedServerFactory(string assemblyPath) => this.assemblyPath = assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath)); + public IsolatedServerFactory(string assemblyPath) + { + this.assemblyPath = assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath)); + + Resolving += IsolatedServerFactory_Resolving; + } + + //https://stackoverflow.com/a/40921746/3976486 + Assembly IsolatedServerFactory_Resolving(AssemblyLoadContext context, AssemblyName assemblyName) + { + if (assemblyName.Name.EndsWith("resources", StringComparison.Ordinal)) + return null; + + var foundDll = Directory.GetFileSystemEntries(assemblyPath, String.Join(".", assemblyName.Name, DllExtension), SearchOption.AllDirectories).FirstOrDefault(); + if (foundDll != default) + return context.LoadFromAssemblyPath(foundDll); + return context.LoadFromAssemblyName(assemblyName); + } /// /// Loads the at and creates an from it @@ -32,26 +52,15 @@ namespace Tgstation.Server.Host.Watchdog /// A new public IServer CreateServer(string[] args, string updatePath) { - //help here: https://stackoverflow.com/questions/40908568/assembly-loading-in-net-core + var assembly = LoadFromAssemblyPath(Path.Combine(assemblyPath, assemblyFileName)); - var oldCd = Environment.CurrentDirectory; - Directory.SetCurrentDirectory(Path.GetDirectoryName(assemblyPath)); - try - { - var assembly = LoadFromAssemblyPath(assemblyPath); + //find the IServerFactory implementation + var serverFactoryInterfaceType = typeof(IServerFactory); + var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); - //find the IServerFactory implementation - var serverFactoryInterfaceType = typeof(IServerFactory); - var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); + var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); - var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); - - return serverFactory.CreateServer(args, updatePath); - } - finally - { - Directory.SetCurrentDirectory(oldCd); - } + return serverFactory.CreateServer(args, updatePath); } //honestly have no idea what this is for, but the examples i see just return null and it seems to work just fine diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index 0b09864c0e..138e10d50f 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index e1bad698f4..a481118eef 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -46,7 +46,6 @@ namespace Tgstation.Server.Host.Watchdog const string DefaultAssemblyPath = "Default"; var assemblyStoragePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "lib"); - var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); logger.LogInformation("Host watchdog starting..."); @@ -60,7 +59,7 @@ namespace Tgstation.Server.Host.Watchdog logger.LogTrace("Atttempting to create new server factory..."); Guid updateGuid; { //forces serverFactory out of the picture once the scope ends - var serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(Path.Combine(nextAssemblyPath, assemblyName)); + var serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(nextAssemblyPath); using (var server = serverFactory.CreateServer(args, assemblyStoragePath)) { logger.LogTrace("Running server..."); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index aee2ab7feb..689ee11fba 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Core builder.EnableSensitiveDataLogging(); }; - switch (databaseConfiguration.DatabaseType) + switch (databaseConfiguration?.DatabaseType ?? DatabaseType.Sqlite) { case DatabaseType.MySql: services.AddDbContext(ConfigureDatabase); diff --git a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj index b44b760e2c..6baddb56cb 100644 --- a/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj +++ b/tests/Tgstation.Server.Host.Service.Tests/Tgstation.Server.Host.Service.Tests.csproj @@ -28,6 +28,7 @@ DEBUG;TRACE prompt 4 + 7.1 pdbonly @@ -36,6 +37,7 @@ TRACE prompt 4 + 7.1 diff --git a/tgstation-server.sln b/tgstation-server.sln index 779617bbd2..360c9998b3 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -102,6 +102,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v4", "v4", "{057FAC33-CC31- src\DMAPI\tgs\v4\commands.dm = src\DMAPI\tgs\v4\commands.dm EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Console.Debug", "src\Tgstation.Server.Host.Console.Debug\Tgstation.Server.Host.Console.Debug.csproj", "{15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -211,6 +213,12 @@ Global {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.Build.0 = Release|Any CPU + {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Docker|Any CPU.ActiveCfg = Debug|Any CPU + {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Docker|Any CPU.Build.0 = Debug|Any CPU + {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From c41ef5e489d48d46692a4bf7aa7c4b2103ed00ab Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 10:59:44 -0400 Subject: [PATCH 321/397] Delete the legacy folder --- legacy/V2/Fix Errors.bat | 24 - legacy/V2/Merge pr without updating.bat | 112 - legacy/V2/OnServerBoot.bat | 47 - legacy/V2/README.md | 111 - legacy/V2/Recompile.bat | 51 - legacy/V2/Reset and Recompile.bat | 66 - legacy/V2/Start Bot.bat | 19 - legacy/V2/Start Server.bat | 39 - legacy/V2/Update Server.bat | 99 - legacy/V2/Update to PR.bat | 121 - legacy/V2/Update without resetting.bat | 100 - legacy/V2/bin/activepr.bat | 1 - legacy/V2/bin/build.bat | 25 - legacy/V2/bin/copyfromgit.bat | 21 - legacy/V2/bin/findab.bat | 45 - legacy/V2/bin/findbyond.bat | 26 - legacy/V2/bin/findgit.bat | 31 - legacy/V2/bin/getcurdate.bat | 2 - legacy/V2/bin/getunixtime.bat | 12 - legacy/V2/bin/unixtime.vbs | 1 - legacy/V2/bin/updategit.bat | 12 - legacy/V2/config.bat | 42 - legacy/V2/copyexclude.txt | 9 - legacy/V2/disable crash dialog.reg | 4 - legacy/V2/install.bat | 81 - legacy/V3/.gitignore | 16 - legacy/V3/AssemblyInfo.global.cs | 10 - legacy/V3/DMAPI/server_tools.dm | 129 - legacy/V3/DMAPI/st_commands.dm | 76 - legacy/V3/DMAPI/st_interface.dm | 135 - legacy/V3/README.md | 240 -- legacy/V3/TGS.CommandLine/AdminCommands.cs | 114 - legacy/V3/TGS.CommandLine/App.config | 6 - legacy/V3/TGS.CommandLine/BYONDCommands.cs | 138 - legacy/V3/TGS.CommandLine/ChatCommands.cs | 721 ----- legacy/V3/TGS.CommandLine/ConfigCommands.cs | 174 -- legacy/V3/TGS.CommandLine/ConsoleCommand.cs | 16 - legacy/V3/TGS.CommandLine/DDCommands.cs | 314 --- legacy/V3/TGS.CommandLine/DMCommands.cs | 218 -- legacy/V3/TGS.CommandLine/Program.cs | 281 -- .../Properties/AssemblyInfo.cs | 17 - legacy/V3/TGS.CommandLine/RepoCommands.cs | 407 --- legacy/V3/TGS.CommandLine/RootCommands.cs | 192 -- legacy/V3/TGS.CommandLine/ServiceCommands.cs | 434 --- .../V3/TGS.CommandLine/TGS.CommandLine.csproj | 74 - legacy/V3/TGS.CommandLine/tgs.ico | Bin 123198 -> 0 bytes legacy/V3/TGS.ControlPanel/App.config | 71 - .../ControlPanel/ByondPage.cs | 100 - .../TGS.ControlPanel/ControlPanel/ChatPage.cs | 218 -- .../ControlPanel/ControlPanel.Designer.cs | 1882 ------------- .../ControlPanel/ControlPanel.cs | 116 - .../ControlPanel/ControlPanel.resx | 2185 --------------- .../TGS.ControlPanel/ControlPanel/RepoPage.cs | 352 --- .../ControlPanel/ServerPage.cs | 499 ---- .../ControlPanel/StaticPage.cs | 356 --- legacy/V3/TGS.ControlPanel/CountedForm.cs | 38 - .../GitHubLoginPrompt.Designer.cs | 176 -- .../V3/TGS.ControlPanel/GitHubLoginPrompt.cs | 103 - .../TGS.ControlPanel/GitHubLoginPrompt.resx | 120 - .../InstanceSelector.Designer.cs | 164 -- .../V3/TGS.ControlPanel/InstanceSelector.cs | 229 -- .../V3/TGS.ControlPanel/InstanceSelector.resx | 2179 --------------- legacy/V3/TGS.ControlPanel/Login.Designer.cs | 245 -- legacy/V3/TGS.ControlPanel/Login.cs | 190 -- legacy/V3/TGS.ControlPanel/Login.resx | 2179 --------------- legacy/V3/TGS.ControlPanel/Program.cs | 135 - .../Properties/AssemblyInfo.cs | 16 - .../Properties/Settings.Designer.cs | 121 - .../Properties/Settings.settings | 30 - legacy/V3/TGS.ControlPanel/ServerOpForm.cs | 35 - .../TGS.ControlPanel/TGS.ControlPanel.csproj | 151 - .../TestMergeManager.Designer.cs | 222 -- .../V3/TGS.ControlPanel/TestMergeManager.cs | 384 --- .../V3/TGS.ControlPanel/TestMergeManager.resx | 2179 --------------- legacy/V3/TGS.ControlPanel/packages.config | 4 - legacy/V3/TGS.ControlPanel/tgs.ico | Bin 123198 -> 0 bytes legacy/V3/TGS.Installer.UI/App.config | 6 - legacy/V3/TGS.Installer.UI/FodyWeavers.xml | 4 - legacy/V3/TGS.Installer.UI/Main.Designer.cs | 227 -- legacy/V3/TGS.Installer.UI/Main.cs | 461 --- legacy/V3/TGS.Installer.UI/Main.resx | 2179 --------------- legacy/V3/TGS.Installer.UI/Program.cs | 19 - .../Properties/AssemblyInfo.cs | 16 - .../Properties/Resources.Designer.cs | 83 - .../Properties/Resources.resx | 127 - .../TGS.Installer.UI/TGS.Installer.UI.csproj | 116 - legacy/V3/TGS.Installer.UI/app.manifest | 76 - legacy/V3/TGS.Installer.UI/packages.config | 6 - legacy/V3/TGS.Installer.UI/tgs.ico | Bin 123198 -> 0 bytes legacy/V3/TGS.Installer/Product.wxs | 155 -- legacy/V3/TGS.Installer/TGS.Installer.wixproj | 110 - legacy/V3/TGS.Installer/packages.config | 4 - .../TGS.Interface.Bridge/DreamDaemonBridge.cs | 35 - .../V3/TGS.Interface.Bridge/FodyWeavers.xml | 4 - .../Properties/AssemblyInfo.cs | 16 - .../TGS.Interface.Bridge.csproj | 78 - .../V3/TGS.Interface.Bridge/packages.config | 6 - .../AuthenticationHeaderApplicator.cs | 97 - legacy/V3/TGS.Interface/ChatSetupInfo.cs | 344 --- legacy/V3/TGS.Interface/Client.cs | 318 --- legacy/V3/TGS.Interface/Command.cs | 92 - .../Components/Administration.cs | 33 - legacy/V3/TGS.Interface/Components/Byond.cs | 43 - legacy/V3/TGS.Interface/Components/Chat.cs | 41 - .../V3/TGS.Interface/Components/Compiler.cs | 61 - legacy/V3/TGS.Interface/Components/Config.cs | 58 - .../TGS.Interface/Components/Connectivity.cs | 17 - .../TGS.Interface/Components/DreamDaemon.cs | 146 - .../V3/TGS.Interface/Components/Instance.cs | 25 - .../Components/InstanceManager.cs | 62 - legacy/V3/TGS.Interface/Components/Interop.cs | 19 - legacy/V3/TGS.Interface/Components/Landing.cs | 26 - .../V3/TGS.Interface/Components/Repository.cs | 204 -- legacy/V3/TGS.Interface/Components/Service.cs | 57 - legacy/V3/TGS.Interface/Definitions.cs | 32 - legacy/V3/TGS.Interface/Enumerations.cs | 178 -- legacy/V3/TGS.Interface/Helpers.cs | 49 - legacy/V3/TGS.Interface/IClient.cs | 44 - legacy/V3/TGS.Interface/IInstance.cs | 55 - legacy/V3/TGS.Interface/IServer.cs | 39 - legacy/V3/TGS.Interface/Instance.cs | 119 - legacy/V3/TGS.Interface/InstanceMetadata.cs | 33 - .../TGS.Interface/Properties/AssemblyInfo.cs | 16 - legacy/V3/TGS.Interface/PullRequestInfo.cs | 58 - legacy/V3/TGS.Interface/RemoteLoginInfo.cs | 132 - legacy/V3/TGS.Interface/RootCommand.cs | 105 - legacy/V3/TGS.Interface/Server.cs | 96 - legacy/V3/TGS.Interface/TGS.Interface.csproj | 92 - legacy/V3/TGS.Interface/TGS.Interface.nuspec | 16 - legacy/V3/TGS.Interface/packages.config | 4 - legacy/V3/TGS.Interface/tgs.ico | Bin 123198 -> 0 bytes legacy/V3/TGS.Server.Console/App.config | 6 - legacy/V3/TGS.Server.Console/Console.cs | 73 - .../Properties/AssemblyInfo.cs | 16 - .../TGS.Server.Console.csproj | 89 - legacy/V3/TGS.Server.Console/app.manifest | 76 - legacy/V3/TGS.Server.Console/tgs.ico | Bin 123198 -> 0 bytes legacy/V3/TGS.Server.Service/App.config | 6 - .../V3/TGS.Server.Service/ProjectInstaller.cs | 35 - .../Properties/AssemblyInfo.cs | 16 - legacy/V3/TGS.Server.Service/Service.cs | 71 - .../TGS.Server.Service.csproj | 69 - legacy/V3/TGS.Server.Service/tgs.ico | Bin 123198 -> 0 bytes legacy/V3/TGS.Server/App.config | 8 - .../TGS.Server/ChatCommands/ByondCommand.cs | 44 - .../V3/TGS.Server/ChatCommands/ChatCommand.cs | 45 - .../V3/TGS.Server/ChatCommands/CommandInfo.cs | 25 - .../V3/TGS.Server/ChatCommands/KekCommand.cs | 31 - .../ChatCommands/PullRequestsCommand.cs | 45 - .../ChatCommands/RevisionCommand.cs | 37 - .../ChatCommands/RootChatCommand.cs | 25 - .../ChatCommands/ServerChatCommand.cs | 46 - .../TGS.Server/ChatCommands/VersionCommand.cs | 31 - .../TGS.Server/ChatProviders/ChatProvider.cs | 77 - .../ChatProviders/DiscordChatProvider.cs | 328 --- .../ChatProviders/IRCChatProvider.cs | 377 --- .../V3/TGS.Server/DeprecatedInstanceConfig.cs | 45 - legacy/V3/TGS.Server/EventID.cs | 335 --- legacy/V3/TGS.Server/Helpers.cs | 213 -- legacy/V3/TGS.Server/ILogger.cs | 40 - .../V3/TGS.Server/Instance/Administration.cs | 202 -- legacy/V3/TGS.Server/Instance/Byond.cs | 381 --- legacy/V3/TGS.Server/Instance/Chat.cs | 248 -- legacy/V3/TGS.Server/Instance/Compiler.cs | 684 ----- legacy/V3/TGS.Server/Instance/Config.cs | 250 -- legacy/V3/TGS.Server/Instance/DreamDaemon.cs | 768 ----- legacy/V3/TGS.Server/Instance/Instance.cs | 186 -- legacy/V3/TGS.Server/Instance/Interop.cs | 281 -- .../TGS.Server/Instance/PreactionHandler.cs | 102 - legacy/V3/TGS.Server/Instance/Repository.cs | 1464 ---------- legacy/V3/TGS.Server/InstanceConfig.cs | 232 -- legacy/V3/TGS.Server/MessageType.cs | 28 - legacy/V3/TGS.Server/ProcessExtension.cs | 72 - .../V3/TGS.Server/Properties/AssemblyInfo.cs | 16 - legacy/V3/TGS.Server/RepoConfig.cs | 151 - .../Security/AuthenticationHeaderDecoder.cs | 59 - .../Security/RootAuthorizationManager.cs | 41 - .../Security/WindowsAuthorizationPolicy.cs | 50 - legacy/V3/TGS.Server/Server.cs | 700 ----- legacy/V3/TGS.Server/ServerConfig.cs | 65 - legacy/V3/TGS.Server/TGS.Server.csproj | 147 - legacy/V3/TGS.Server/packages.config | 13 - legacy/V3/TGS.Server/tgs.ico | Bin 123198 -> 0 bytes .../Repository/RepositoryCommandTest.cs | 22 - .../TestRepoSetPushTestmergeCommitsCommand.cs | 53 - .../Repository/TestRepoStatusCommand.cs | 46 - .../ControlPanel/TestInstanceSelector.cs | 28 - .../V3/TGS.Tests/OutputProcOverriderTest.cs | 26 - .../V3/TGS.Tests/Properties/AssemblyInfo.cs | 9 - legacy/V3/TGS.Tests/Server/TestHelpers.cs | 35 - .../TGS.Tests/ServiceInterface/TestHelpers.cs | 53 - .../ServiceInterface/TestInterface.cs | 97 - legacy/V3/TGS.Tests/TGS.Tests.csproj | 111 - .../V3/TGS.Tests/TempDirectoryRequiredTest.cs | 42 - legacy/V3/TGS.Tests/packages.config | 7 - legacy/V3/TGS3.json | 58 - legacy/V3/TGStationServer3.sln | 188 -- legacy/V3/Tools/BuildDox.ps1 | 36 - legacy/V3/Tools/Config.dm | 8 - .../V3/Tools/CoverageExclusions.runsettings | 28 - legacy/V3/Tools/DMAPITravisTester.dme | 19 - legacy/V3/Tools/Doxyfile | 2479 ----------------- legacy/V3/Tools/PostCIBuild.ps1 | 77 - legacy/V3/Tools/SignBasics.ps1 | 18 - legacy/V3/Tools/SignMSI.ps1 | 13 - legacy/V3/Tools/Test.dm | 42 - legacy/V3/Tools/UploadCoverage.ps1 | 10 - legacy/V3/Tools/ViewTGS3Logs.xml | 1 - legacy/V3/Tools/build_byond.sh | 19 - legacy/V3/Tools/install_byond.sh | 21 - legacy/V3/Tools/tgstation13.org.pfx | Bin 2669 -> 0 bytes legacy/V3/tgs.ico | Bin 123198 -> 0 bytes 212 files changed, 37826 deletions(-) delete mode 100644 legacy/V2/Fix Errors.bat delete mode 100644 legacy/V2/Merge pr without updating.bat delete mode 100644 legacy/V2/OnServerBoot.bat delete mode 100644 legacy/V2/README.md delete mode 100644 legacy/V2/Recompile.bat delete mode 100644 legacy/V2/Reset and Recompile.bat delete mode 100644 legacy/V2/Start Bot.bat delete mode 100644 legacy/V2/Start Server.bat delete mode 100644 legacy/V2/Update Server.bat delete mode 100644 legacy/V2/Update to PR.bat delete mode 100644 legacy/V2/Update without resetting.bat delete mode 100644 legacy/V2/bin/activepr.bat delete mode 100644 legacy/V2/bin/build.bat delete mode 100644 legacy/V2/bin/copyfromgit.bat delete mode 100644 legacy/V2/bin/findab.bat delete mode 100644 legacy/V2/bin/findbyond.bat delete mode 100644 legacy/V2/bin/findgit.bat delete mode 100644 legacy/V2/bin/getcurdate.bat delete mode 100644 legacy/V2/bin/getunixtime.bat delete mode 100644 legacy/V2/bin/unixtime.vbs delete mode 100644 legacy/V2/bin/updategit.bat delete mode 100644 legacy/V2/config.bat delete mode 100644 legacy/V2/copyexclude.txt delete mode 100644 legacy/V2/disable crash dialog.reg delete mode 100644 legacy/V2/install.bat delete mode 100644 legacy/V3/.gitignore delete mode 100644 legacy/V3/AssemblyInfo.global.cs delete mode 100644 legacy/V3/DMAPI/server_tools.dm delete mode 100644 legacy/V3/DMAPI/st_commands.dm delete mode 100644 legacy/V3/DMAPI/st_interface.dm delete mode 100644 legacy/V3/README.md delete mode 100644 legacy/V3/TGS.CommandLine/AdminCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/App.config delete mode 100644 legacy/V3/TGS.CommandLine/BYONDCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/ChatCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/ConfigCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/ConsoleCommand.cs delete mode 100644 legacy/V3/TGS.CommandLine/DDCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/DMCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/Program.cs delete mode 100644 legacy/V3/TGS.CommandLine/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.CommandLine/RepoCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/RootCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/ServiceCommands.cs delete mode 100644 legacy/V3/TGS.CommandLine/TGS.CommandLine.csproj delete mode 100644 legacy/V3/TGS.CommandLine/tgs.ico delete mode 100644 legacy/V3/TGS.ControlPanel/App.config delete mode 100644 legacy/V3/TGS.ControlPanel/ControlPanel/ByondPage.cs delete mode 100644 legacy/V3/TGS.ControlPanel/ControlPanel/ChatPage.cs delete mode 100644 legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.Designer.cs delete mode 100644 legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.cs delete mode 100644 legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.resx delete mode 100644 legacy/V3/TGS.ControlPanel/ControlPanel/RepoPage.cs delete mode 100644 legacy/V3/TGS.ControlPanel/ControlPanel/ServerPage.cs delete mode 100644 legacy/V3/TGS.ControlPanel/ControlPanel/StaticPage.cs delete mode 100644 legacy/V3/TGS.ControlPanel/CountedForm.cs delete mode 100644 legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.Designer.cs delete mode 100644 legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.cs delete mode 100644 legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.resx delete mode 100644 legacy/V3/TGS.ControlPanel/InstanceSelector.Designer.cs delete mode 100644 legacy/V3/TGS.ControlPanel/InstanceSelector.cs delete mode 100644 legacy/V3/TGS.ControlPanel/InstanceSelector.resx delete mode 100644 legacy/V3/TGS.ControlPanel/Login.Designer.cs delete mode 100644 legacy/V3/TGS.ControlPanel/Login.cs delete mode 100644 legacy/V3/TGS.ControlPanel/Login.resx delete mode 100644 legacy/V3/TGS.ControlPanel/Program.cs delete mode 100644 legacy/V3/TGS.ControlPanel/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.ControlPanel/Properties/Settings.Designer.cs delete mode 100644 legacy/V3/TGS.ControlPanel/Properties/Settings.settings delete mode 100644 legacy/V3/TGS.ControlPanel/ServerOpForm.cs delete mode 100644 legacy/V3/TGS.ControlPanel/TGS.ControlPanel.csproj delete mode 100644 legacy/V3/TGS.ControlPanel/TestMergeManager.Designer.cs delete mode 100644 legacy/V3/TGS.ControlPanel/TestMergeManager.cs delete mode 100644 legacy/V3/TGS.ControlPanel/TestMergeManager.resx delete mode 100644 legacy/V3/TGS.ControlPanel/packages.config delete mode 100644 legacy/V3/TGS.ControlPanel/tgs.ico delete mode 100644 legacy/V3/TGS.Installer.UI/App.config delete mode 100644 legacy/V3/TGS.Installer.UI/FodyWeavers.xml delete mode 100644 legacy/V3/TGS.Installer.UI/Main.Designer.cs delete mode 100644 legacy/V3/TGS.Installer.UI/Main.cs delete mode 100644 legacy/V3/TGS.Installer.UI/Main.resx delete mode 100644 legacy/V3/TGS.Installer.UI/Program.cs delete mode 100644 legacy/V3/TGS.Installer.UI/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.Installer.UI/Properties/Resources.Designer.cs delete mode 100644 legacy/V3/TGS.Installer.UI/Properties/Resources.resx delete mode 100644 legacy/V3/TGS.Installer.UI/TGS.Installer.UI.csproj delete mode 100644 legacy/V3/TGS.Installer.UI/app.manifest delete mode 100644 legacy/V3/TGS.Installer.UI/packages.config delete mode 100644 legacy/V3/TGS.Installer.UI/tgs.ico delete mode 100644 legacy/V3/TGS.Installer/Product.wxs delete mode 100644 legacy/V3/TGS.Installer/TGS.Installer.wixproj delete mode 100644 legacy/V3/TGS.Installer/packages.config delete mode 100644 legacy/V3/TGS.Interface.Bridge/DreamDaemonBridge.cs delete mode 100644 legacy/V3/TGS.Interface.Bridge/FodyWeavers.xml delete mode 100644 legacy/V3/TGS.Interface.Bridge/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.Interface.Bridge/TGS.Interface.Bridge.csproj delete mode 100644 legacy/V3/TGS.Interface.Bridge/packages.config delete mode 100644 legacy/V3/TGS.Interface/AuthenticationHeaderApplicator.cs delete mode 100644 legacy/V3/TGS.Interface/ChatSetupInfo.cs delete mode 100644 legacy/V3/TGS.Interface/Client.cs delete mode 100644 legacy/V3/TGS.Interface/Command.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Administration.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Byond.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Chat.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Compiler.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Config.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Connectivity.cs delete mode 100644 legacy/V3/TGS.Interface/Components/DreamDaemon.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Instance.cs delete mode 100644 legacy/V3/TGS.Interface/Components/InstanceManager.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Interop.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Landing.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Repository.cs delete mode 100644 legacy/V3/TGS.Interface/Components/Service.cs delete mode 100644 legacy/V3/TGS.Interface/Definitions.cs delete mode 100644 legacy/V3/TGS.Interface/Enumerations.cs delete mode 100644 legacy/V3/TGS.Interface/Helpers.cs delete mode 100644 legacy/V3/TGS.Interface/IClient.cs delete mode 100644 legacy/V3/TGS.Interface/IInstance.cs delete mode 100644 legacy/V3/TGS.Interface/IServer.cs delete mode 100644 legacy/V3/TGS.Interface/Instance.cs delete mode 100644 legacy/V3/TGS.Interface/InstanceMetadata.cs delete mode 100644 legacy/V3/TGS.Interface/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.Interface/PullRequestInfo.cs delete mode 100644 legacy/V3/TGS.Interface/RemoteLoginInfo.cs delete mode 100644 legacy/V3/TGS.Interface/RootCommand.cs delete mode 100644 legacy/V3/TGS.Interface/Server.cs delete mode 100644 legacy/V3/TGS.Interface/TGS.Interface.csproj delete mode 100644 legacy/V3/TGS.Interface/TGS.Interface.nuspec delete mode 100644 legacy/V3/TGS.Interface/packages.config delete mode 100644 legacy/V3/TGS.Interface/tgs.ico delete mode 100644 legacy/V3/TGS.Server.Console/App.config delete mode 100644 legacy/V3/TGS.Server.Console/Console.cs delete mode 100644 legacy/V3/TGS.Server.Console/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.Server.Console/TGS.Server.Console.csproj delete mode 100644 legacy/V3/TGS.Server.Console/app.manifest delete mode 100644 legacy/V3/TGS.Server.Console/tgs.ico delete mode 100644 legacy/V3/TGS.Server.Service/App.config delete mode 100644 legacy/V3/TGS.Server.Service/ProjectInstaller.cs delete mode 100644 legacy/V3/TGS.Server.Service/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.Server.Service/Service.cs delete mode 100644 legacy/V3/TGS.Server.Service/TGS.Server.Service.csproj delete mode 100644 legacy/V3/TGS.Server.Service/tgs.ico delete mode 100644 legacy/V3/TGS.Server/App.config delete mode 100644 legacy/V3/TGS.Server/ChatCommands/ByondCommand.cs delete mode 100644 legacy/V3/TGS.Server/ChatCommands/ChatCommand.cs delete mode 100644 legacy/V3/TGS.Server/ChatCommands/CommandInfo.cs delete mode 100644 legacy/V3/TGS.Server/ChatCommands/KekCommand.cs delete mode 100644 legacy/V3/TGS.Server/ChatCommands/PullRequestsCommand.cs delete mode 100644 legacy/V3/TGS.Server/ChatCommands/RevisionCommand.cs delete mode 100644 legacy/V3/TGS.Server/ChatCommands/RootChatCommand.cs delete mode 100644 legacy/V3/TGS.Server/ChatCommands/ServerChatCommand.cs delete mode 100644 legacy/V3/TGS.Server/ChatCommands/VersionCommand.cs delete mode 100644 legacy/V3/TGS.Server/ChatProviders/ChatProvider.cs delete mode 100644 legacy/V3/TGS.Server/ChatProviders/DiscordChatProvider.cs delete mode 100644 legacy/V3/TGS.Server/ChatProviders/IRCChatProvider.cs delete mode 100644 legacy/V3/TGS.Server/DeprecatedInstanceConfig.cs delete mode 100644 legacy/V3/TGS.Server/EventID.cs delete mode 100644 legacy/V3/TGS.Server/Helpers.cs delete mode 100644 legacy/V3/TGS.Server/ILogger.cs delete mode 100644 legacy/V3/TGS.Server/Instance/Administration.cs delete mode 100644 legacy/V3/TGS.Server/Instance/Byond.cs delete mode 100644 legacy/V3/TGS.Server/Instance/Chat.cs delete mode 100644 legacy/V3/TGS.Server/Instance/Compiler.cs delete mode 100644 legacy/V3/TGS.Server/Instance/Config.cs delete mode 100644 legacy/V3/TGS.Server/Instance/DreamDaemon.cs delete mode 100644 legacy/V3/TGS.Server/Instance/Instance.cs delete mode 100644 legacy/V3/TGS.Server/Instance/Interop.cs delete mode 100644 legacy/V3/TGS.Server/Instance/PreactionHandler.cs delete mode 100644 legacy/V3/TGS.Server/Instance/Repository.cs delete mode 100644 legacy/V3/TGS.Server/InstanceConfig.cs delete mode 100644 legacy/V3/TGS.Server/MessageType.cs delete mode 100644 legacy/V3/TGS.Server/ProcessExtension.cs delete mode 100644 legacy/V3/TGS.Server/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.Server/RepoConfig.cs delete mode 100644 legacy/V3/TGS.Server/Security/AuthenticationHeaderDecoder.cs delete mode 100644 legacy/V3/TGS.Server/Security/RootAuthorizationManager.cs delete mode 100644 legacy/V3/TGS.Server/Security/WindowsAuthorizationPolicy.cs delete mode 100644 legacy/V3/TGS.Server/Server.cs delete mode 100644 legacy/V3/TGS.Server/ServerConfig.cs delete mode 100644 legacy/V3/TGS.Server/TGS.Server.csproj delete mode 100644 legacy/V3/TGS.Server/packages.config delete mode 100644 legacy/V3/TGS.Server/tgs.ico delete mode 100644 legacy/V3/TGS.Tests/CommandLine/Commands/Repository/RepositoryCommandTest.cs delete mode 100644 legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs delete mode 100644 legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs delete mode 100644 legacy/V3/TGS.Tests/ControlPanel/TestInstanceSelector.cs delete mode 100644 legacy/V3/TGS.Tests/OutputProcOverriderTest.cs delete mode 100644 legacy/V3/TGS.Tests/Properties/AssemblyInfo.cs delete mode 100644 legacy/V3/TGS.Tests/Server/TestHelpers.cs delete mode 100644 legacy/V3/TGS.Tests/ServiceInterface/TestHelpers.cs delete mode 100644 legacy/V3/TGS.Tests/ServiceInterface/TestInterface.cs delete mode 100644 legacy/V3/TGS.Tests/TGS.Tests.csproj delete mode 100644 legacy/V3/TGS.Tests/TempDirectoryRequiredTest.cs delete mode 100644 legacy/V3/TGS.Tests/packages.config delete mode 100644 legacy/V3/TGS3.json delete mode 100644 legacy/V3/TGStationServer3.sln delete mode 100644 legacy/V3/Tools/BuildDox.ps1 delete mode 100644 legacy/V3/Tools/Config.dm delete mode 100644 legacy/V3/Tools/CoverageExclusions.runsettings delete mode 100644 legacy/V3/Tools/DMAPITravisTester.dme delete mode 100644 legacy/V3/Tools/Doxyfile delete mode 100644 legacy/V3/Tools/PostCIBuild.ps1 delete mode 100644 legacy/V3/Tools/SignBasics.ps1 delete mode 100644 legacy/V3/Tools/SignMSI.ps1 delete mode 100644 legacy/V3/Tools/Test.dm delete mode 100644 legacy/V3/Tools/UploadCoverage.ps1 delete mode 100644 legacy/V3/Tools/ViewTGS3Logs.xml delete mode 100644 legacy/V3/Tools/build_byond.sh delete mode 100644 legacy/V3/Tools/install_byond.sh delete mode 100644 legacy/V3/Tools/tgstation13.org.pfx delete mode 100644 legacy/V3/tgs.ico diff --git a/legacy/V2/Fix Errors.bat b/legacy/V2/Fix Errors.bat deleted file mode 100644 index bbe540c82c..0000000000 --- a/legacy/V2/Fix Errors.bat +++ /dev/null @@ -1,24 +0,0 @@ -@echo off -title Automated Error Fixer. -echo This will reset some things, the byond server (DreamDaemon) and the start server script must not be running. Some error messages are normal. -echo You will be prompted to press any key 3 times -pause -pause -pause -cls -echo Resetting folders -mkdir gamecode\a -mkdir gamecode\b -del /S /F /Q gamefolder >nul 2>nul -rmdir /S /q gamefolder -mklink /d gamefolder gamecode\a -call bin\findab.bat -cls -echo Re-Initializing code -call bin\copyfromgit.bat -cls -echo Recompiling the game. If you plan to update or testmerge you can just close this program now and continue. -call bin\build.bat -cls -echo Done! (hopefully) -pause \ No newline at end of file diff --git a/legacy/V2/Merge pr without updating.bat b/legacy/V2/Merge pr without updating.bat deleted file mode 100644 index 813116a006..0000000000 --- a/legacy/V2/Merge pr without updating.bat +++ /dev/null @@ -1,112 +0,0 @@ -@echo off -@title Server Updater -SETLOCAL ENABLEDELAYEDEXPANSION -set HOME = %USERPROFILE% - -call config.bat -rem if the first arg to nudge.py is not a channel, it is treated as the "source" -if not defined UPDATE_LOG_CHANNEL set UPDATE_LOG_CHANNEL="UPDATER" -call bin\getcurdate.bat -call bin\findgit.bat -echo This will handle merging a pr locally, compiling the server, and applying the PR test job. - -:PROMPT -SET /P UserInput=Please enter the pr number (without a # or anything of the sorts): -SET /A PR=UserInput - -if %PR% EQU %UserInput% ( - if %PR% GTR 0 ( - echo updating to pr %PR% - ) else ( - echo Bad input - goto PROMPT - ) -) else ( - echo Bad input - goto PROMPT -) -if exist updating.lk ( - echo ERROR: A current update script has been detected running. if you know this is a mistake: - pause - echo Please be double sure that an update script is not currently running, if you think one might be, close this window. otherwise: - pause -) - -echo lock>updating.lk - -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "PR test job started. Merging PR #%PR% locally" >nul 2>nul - - -cd gitrepo -git fetch -f origin pull/%PR%/head:pr-%PR% -if %ERRORLEVEL% neq 0 ( - cd .. - echo git fetch failed. Aborting test merge. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Git fetch failed. Aborting test merge" - del updating.lk >nul 2>nul - pause - exit /b 1 -) -git merge pr-%PR% -if %ERRORLEVEL% neq 0 ( - cd .. - echo git merge of PR #%PR% failed. Aborting test merge - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "git merge of PR #%PR% failed. Aborting PR test merge" >nul 2>nul - cd gitrepo - git merge --abort - if !ERRORLEVEL! neq 0 ( - echo ERROR: Error aborting test merge, resetting repo. - cd .. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Error aborting test merge, resetting git repo to head" >nul 2>nul - cd gitrepo - git reset --hard - git clean -fd - bin/activepr.bat - del /F /Q prtestjob.lk >nul 2>nul - echo NOTICE: We had to reset the repo's state, all other active test merges were undone. - ) - cd .. - del updating.lk >nul 2>nul - pause - exit /b 1 -) -cd .. - -echo %PR%>>prtestjob.lk -> bin/activepr.bat - -echo ################################## -echo ################################## -echo: -echo Test merging pr done, compiling in 10 seconds. If you want to preform other actions (like more test merges) You can close this now and do them. -echo: -del updating.lk >nul 2>nul - -timeout 10 - -echo lock>updating.lk -call bin\findab.bat - -call bin\copyfromgit.bat - -echo compiling change log -cd gamecode\%AB% -call python tools\ss13_genchangelog.py html/changelog.html html/changelogs -cd ..\.. - -echo Compiling game. -call bin\build.bat -if %DM_EXIT% neq 0 ( - echo DM compile failed. Aborting. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "DM compile failed. Aborting test merge." >nul 2>nul - del /F /Q updating.lk >nul 2>nul - pause - exit /b 1 -) - -del updating.lk >nul 2>nul -rmdir /q gamefolder -mklink /d gamefolder gamecode\%AB% >nul -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Test merge job finished. Test merge will take place next round." >nul 2>nul -echo Done. The test merge will automatically take place at round restart. -timeout 300 diff --git a/legacy/V2/OnServerBoot.bat b/legacy/V2/OnServerBoot.bat deleted file mode 100644 index db1f5cc230..0000000000 --- a/legacy/V2/OnServerBoot.bat +++ /dev/null @@ -1,47 +0,0 @@ -@echo off -title Server Boot Detected. -echo This script is only meant to start byond when the server first boots, do not manually run this. -echo If you manually ran this, close this window NOW. -timeout 15 -start cmd /c "Start Bot.bat" -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Prepping code -mkdir gamecode\a -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Prepping code -mkdir gamecode\b -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Prepping code -del /S /F /Q gamefolder >nul 2>nul -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Prepping code -rmdir /S /q gamefolder -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Prepping code -mklink /d gamefolder gamecode\a -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Prepping code -call bin\findab.bat -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Re-Initializing code -call bin\copyfromgit.bat -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Re-Initializing code -cls -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Compiling the game. If you plan to update or testmerge you can just close this program now and continue. -call bin\build.bat -cls -echo Server boot detected. Starting byond and Space Station 13. -echo Starting server and bot. -start cmd /c "Start Server.bat" -timeout 10 \ No newline at end of file diff --git a/legacy/V2/README.md b/legacy/V2/README.md deleted file mode 100644 index 10831fd461..0000000000 --- a/legacy/V2/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# Tgstation Toolkit: -This is a toolset to manage a production server of /tg/Station13 (and its forks). It includes an update script that is able to update the server without having to stop or shutdown the server (the update will take effect next round) and a script to start the server and restart it if it crashes (optional, requires registry tweaks to disable the Windows crash dialog system wide) as well as systems for fixing errors and merging GitHub Pull Requests locally. - -Generally, updates force a live tracking of the configured git repo, resetting local modifications. If you plan to make modifications, set up a new git repo to store your version of the code in, and point this script to that in the config (explained below). This can be on github or a local repo using file:/// urls. - -These tools require UAC to be disabled. (There has been limited luck getting it to work under UAC on windows 10 using run as admin, but in other versions of windows, running as admin resets the current directory and this breaks things) -(Note: There is no security risk to disabling UAC because UAC is not a security boundary, there exists active unpatched exploits against it that have been around since vista and work up to windows 10) - -## Install: -1. Move this folder to where you want your server to run from (you may also rename this folder if you wish) -1. Right click on `config.bat` and select `Edit`. -1. Configure the port, git repo, and other setting, You may configure the location of git, but if you installed using git for windows, we will auto detect it if its not in path. -1. You may also need to change the location of the git repo url, and if you renamed your `dme`/"dream maker environment" file, you will need to change the project name configuration setting to be the name of your dme minus the extension (eg: if you renamed `tgstation.dme` to `ntstation.dme`, project name should be set to `ntstation` -1. Finally, run install.bat and hope for no error. - * It will clone the git repo, setup some folders, and add cross reference links everywhere. - -### Optional: -If you plan to use the `Start Server.bat` script to start the server and restart it if it crashes, you need to run the `disable crash dialog.reg` registry script to disable the "program.exe has stopped working" dialog. This will get disabled system wide so if you rely on this dialog, you should skip this step. - -## Usage: -### Folders: -* `gamecode/` - * This will house two copies of the game code, one for updating and one for live. When updating, it will automatically swap them. - -* `gamedata/` - * This contains the `data/` and `config/` folders from the code. They are stored here and a symbolic link is created in the `gamecode/` folders pointing to here. - * This also makes backing them up easy. -(you may copy and paste your existing `data/` and `config/` folders here after the install script has ran.) - -* `bot/` - * This is a copy of the bot folder. you should run the bot from here.a link to nudge.py is created in the code folders so that the game can use the announcement feature. - * The start server script and update script will send messages out thru the bot, but if its not running or python is not installed, they will gracefully continue what they are doing. - -* `gamefolder/` - * This is a symbolic link pointing to current "live" folder. - * When the server is updated, we just point this to the updating folder so that the update takes place next round. - -* `gitrepo/` - * This contains the actual git repository, all changes in here will be overwritten during update operations, the configured branch will always be forced to track from live. - * On the first update of the day, the current code state is saved to a branch called `backup-YYYY-MM-DD` before updating, to make local reverts easy. - * Following update operations on the same day do not make branches because I was too lazy to detect such edge cases. - -* `bin/` - * This contains random helper batch files. - * Running these on their own is a bad idea. - -### Starting the game server: -To run the game server, Run `Start Server.bat` - -It will restart the game server if it shutdowns for any reason, with delays if the game server had recently been (re)started. - - -### Updating the server: -To update the server, just run `Update Server.bat`. (it will git pull, compile, all that jazz) - -(Note: Updating automatically does a code reset, clearing ALL changes to the local git repo, including test merges (explained below) and manual changes (This will not change any configs/settings or clear any data in the `gamedata/` folder)) - -Updates do not require the server to be shutdown, changes will apply next round if the server is currently running. - -Updates create a branch with the current state of the repo called `backup-YYYY-MM-DD` (only one is created in any given day) - -There is also a `Update without resetting.bat` file that does the same without resetting the code, used to update without clearing test merges or local changes. Prone to merge conflicts. - - -### Locally merge GitHub Pull Requests (PR test merge): -This feature currently only works if github is the remote(git server), it could be adapted for gitlab as well. - -Running these will merge the pull request then recompile the server, changes take effect the next round if the server is currently running. - -There are multiple flavors: -* `Update To PR.bat` - * Updates the server, resetting state, and merges a PR(Pull Request) by number. -* `Merge PR Without Updating.bat` - * Merges a PR without updating the server before hand or resetting the state (can be used to test merge multiple PRs). - -You can clear all active test merges using `Reset and Recompile.bat` (explained below) - -### Resetting, Recompiling, and troubleshooting. -* `Recompile.bat` - * Just recompiles the game code and stages it to apply next round -* `Reset and Recompile.bat` - * Like the above but resets the git repo to the state of the last update operation (clearing any changes or test merges) (Does not reset `gamedata/` data/settings) - * Generally used to clear out test merges -* `Fix Errors.bat` - * Requires the server not be running, rebuilds the staging A/B folders and then does all of the above. (Used to fix errors that can prevent the server from starting or cause it to crash on reboot) - -### Using the bot -1. Install python 3 -1. Edit the config in `bot/` as needed -1. Start the bot using the `Start Bot.bat` file - - -### Starting everything when the computer/server boots -1. Use autologin to set it up so the user account logs in at boot - * (There are some security implications with doing this, this is only a tiny bit more secure then putting a file on the hard drive with the server's remote login password titled `totally not a password.txt`) - * https://technet.microsoft.com/en-us/sysinternals/bb963905 -1. Setup something to run OnServerBoot.bat on login (setting up a link/shortcut to this file in the startup folder of the start menu works, creating a scheduled task in windows administrative tools also works) -1. OnServerBoot.bat does not update, but it does re-compile and reinitialize the A/B folders. - -### Updating byond after this is all setup -1. Download installer from byond website -1. Close watch dog/start server script -1. Wait for current round to end -1. Exit DreamDeamon -1. Run installer -1. After installing, run recompile.bat (or update if you want) -1. Run start server.bat - - - - diff --git a/legacy/V2/Recompile.bat b/legacy/V2/Recompile.bat deleted file mode 100644 index 40b746365e..0000000000 --- a/legacy/V2/Recompile.bat +++ /dev/null @@ -1,51 +0,0 @@ -@echo off -@title Server Updater -set HOME = %USERPROFILE% - -call config.bat -call bin\getcurdate.bat - -echo This will handle recompiling the server, and applying the new version. -echo ready? - -timeout 120 - -if exist updating.lk ( - echo ERROR: A current update script has been detected running. if you know this is a mistake: - pause - echo Please be double sure that an update script is not currently running, if you think one might be, close this window. otherwise: - pause -) - -echo lock>updating.lk - -rem if the first arg to nudge.py is not a channel, it is treated as the "source" -if not defined UPDATE_LOG_CHANNEL set UPDATE_LOG_CHANNEL="UPDATER" - -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Recompile job started" >nul 2>nul - -call bin\findab.bat - -call bin\copyfromgit.bat - -echo compiling change log -cd gamecode\%AB% -call python tools\ss13_genchangelog.py html/changelog.html html/changelogs -cd ..\.. - -echo Compiling game. -call bin\build.bat -if %DM_EXIT% neq 0 ( - echo DM compile failed. Aborting. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "DM compile failed Aborting recompile." >nul 2>nul - del /F /Q updating.lk >nul 2>nul - pause - exit /b 1 -) - -del updating.lk >nul 2>nul -rmdir /q gamefolder -mklink /d gamefolder gamecode\%AB% >nul -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Recompile finished. Recompiled code will take place next round." >nul 2>nul -echo Done. The recompile will automatically take place at round restart. -timeout 300 \ No newline at end of file diff --git a/legacy/V2/Reset and Recompile.bat b/legacy/V2/Reset and Recompile.bat deleted file mode 100644 index f0127fd3ac..0000000000 --- a/legacy/V2/Reset and Recompile.bat +++ /dev/null @@ -1,66 +0,0 @@ -@echo off -@title Server Updater -set HOME = %USERPROFILE% - -call config.bat -call bin\getcurdate.bat -call bin\findgit.bat - -echo This will handle resetting the git repo, recompiling the server, and applying the new version. -echo Ready? - -timeout 120 - -if exist updating.lk ( - echo ERROR: A current update script has been detected running. if you know this is a mistake: - pause - echo Please be double sure that an update script is not currently running, if you think one might be, close this window. otherwise: - pause -) - -echo lock>updating.lk - -rem if the first arg to nudge.py is not a channel, it is treated as the "source" -if not defined UPDATE_LOG_CHANNEL set UPDATE_LOG_CHANNEL="UPDATER" - -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Reset local changes job started" >nul 2>nul - -cd gitrepo -git reset --hard -git clean -df -cd .. -echo ################################## -echo ################################## -echo: -echo Resetting done, compiling in 10 seconds. If you want to preform other actions (like test merges) You can close this now and do them. -echo: -del updating.lk >nul 2>nul - -timeout 10 - -echo lock>updating.lk -call bin\findab.bat - -call bin\copyfromgit.bat - -echo compiling change log -cd gamecode\%AB% -call python tools\ss13_genchangelog.py html/changelog.html html/changelogs -cd ..\.. - -echo Compiling game. -call bin\build.bat -if %DM_EXIT% neq 0 ( - echo DM compile failed. Aborting. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "DM compile failed Aborting Reset." >nul 2>nul - del /F /Q updating.lk >nul 2>nul - pause - exit /b 1 -) - -del updating.lk >nul 2>nul -rmdir /q gamefolder -mklink /d gamefolder gamecode\%AB% >nul -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Reset finished. Reset will take place next round." >nul 2>nul -echo Done. The Reset will automatically take place at round restart. -timeout 300 diff --git a/legacy/V2/Start Bot.bat b/legacy/V2/Start Bot.bat deleted file mode 100644 index a3b1cba0d8..0000000000 --- a/legacy/V2/Start Bot.bat +++ /dev/null @@ -1,19 +0,0 @@ -@echo off -@title NT IRC BOT -echo Welcome to the start bot script, This will start the bot and make sure it stays running. This assumes python in the path. To continue, press any key or wait 15 seconds. -timeout 15 -cd bot -:START -call ..\bin\getcurdate.bat -if not exist ..\gamedata\data\logs\bot mkdir ..\gamedata\data\logs\bot\ -cls -echo NT IRC Bot -echo Bot Running. Watching for Bot exits. -start /WAIT python minibot.py ^>^>..\gamedata\data\logs\bot\bot-%CUR_DATE%.txt -cls -echo NT IRC Bot -echo Bot exit detected. Restarting in 15 minutes. -REM this is so long because we want to avoid the bot spamming the server and getting klined/glined/or akilled -timeout 900 - -goto :START diff --git a/legacy/V2/Start Server.bat b/legacy/V2/Start Server.bat deleted file mode 100644 index 0312a22a5c..0000000000 --- a/legacy/V2/Start Server.bat +++ /dev/null @@ -1,39 +0,0 @@ -@echo off -@title SERVER WATCHDOG -call config.bat -call bin\findbyond.bat - -echo Welcome to the start server watch dog script, This will start the server and make sure it stays running. To continue, press any key or wait 30 seconds. -timeout 30 - -if not exist gamedata\data\logs\runtimes mkdir gamedata\data\logs\runtimes\ - -@call python bot\nudge.py "WATCHDOG" "Watch Dog online. Starting server" >nul 2>nul -:START - -call bin\getcurdate.bat - -call bin\getunixtime.bat UNIXTIME - -echo %UNIXTIME% - -set STARTTIME=%UNIXTIME% - -cls -echo Watch Dog. -echo Server Running. Watching for server exits. -start /WAIT /ABOVENORMAL "" dreamdaemon.exe gamefolder\%PROJECTNAME%.dmb -port %PORT% -trusted -close -public -verbose -cls - -call bin\getunixtime.bat UNIXTIME - -SET /A Result=%UNIXTIME% - %STARTTIME% -SET /A Result=180 - (%Result%/3) -if %Result% LSS 0 set /A Result=0 - -echo Watch Dog. -echo Server exit detected. Restarting in %Result% seconds. -@python bot\nudge.py "WATCHDOG" "Server exit detected. Restarting server in %Result% seconds." >nul 2>nul -timeout %Result% - -goto :START diff --git a/legacy/V2/Update Server.bat b/legacy/V2/Update Server.bat deleted file mode 100644 index 1d7d8644af..0000000000 --- a/legacy/V2/Update Server.bat +++ /dev/null @@ -1,99 +0,0 @@ -@echo off -title Server Updater -SETLOCAL ENABLEDELAYEDEXPANSION -set HOME = %USERPROFILE% - -call config.bat -call bin\getcurdate.bat - -echo This will handle downloading git, compiling the server, and applying the update. -echo Ready? - -timeout 120 - -if exist updating.lk ( - echo ERROR: A current update script has been detected running. if you know this is a mistake: - pause - echo Please be double sure that an update script is not currently running, if you think one might be, close this window. otherwise: - pause -) - -if exist prtestjob.lk ( - call bin/activepr.bat - echo WARNING: The server is currently testing the following PRs !PR!. This update would override that. Do you still want to update? Close this window if not, otherwise: - pause -) - -del /F /Q prtestjob.lk >nul 2>nul - -echo lock>updating.lk - bin/activepr.bat - -rem if the first arg to nudge.py is not a channel, it is treated as the "source" -if not defined UPDATE_LOG_CHANNEL set UPDATE_LOG_CHANNEL="UPDATER" - -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Update job started" >nul 2>nul - -call bin\updategit.bat -if %GIT_EXIT% neq 0 ( - echo git pull failed. Aborting update - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Git fetch failed. Aborting update" - del updating.lk >nul 2>nul - pause - exit /b 1 -) - -if defined PUSHCHANGELOGTOGIT ( - cd gitrepo - echo compiling change log - python tools\ss13_genchangelog.py html/changelog.html html/changelogs - if !ERRORLEVEL! == 0 ( - echo pushing compiled changelog to server - git add -u html/changelog.html - git add -u html/changelogs - git commit -m "Automatic changelog compile, [ci skip]" - if !ERRORLEVEL! == 0 ( - git push - ) - REM an error here generally means there was nothing to commit. - ) - cd .. -) - -echo ################################## -echo ################################## -echo: -echo Updating done, compiling in 10 seconds. If you want to preform other actions (like test merge) You can close this now and do them. -echo: -del updating.lk >nul 2>nul - -timeout 10 - -echo lock>updating.lk -call bin\findab.bat - -call bin\copyfromgit.bat - -if not defined PUSHCHANGELOGTOGIT ( - echo compiling change log - cd gamecode\%AB% - call python tools\ss13_genchangelog.py html/changelog.html html/changelogs - cd ..\.. -) - -echo Compiling game. -call bin\build.bat -if %DM_EXIT% neq 0 ( - echo DM compile failed. Aborting. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "DM compile failed Aborting update." >nul 2>nul - del /F /Q updating.lk >nul 2>nul - pause - exit /b 1 -) - -del updating.lk >nul 2>nul -rmdir /q gamefolder -mklink /d gamefolder gamecode\%AB% >nul -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Update job finished. Update will take place next round." >nul 2>nul -echo Done. The update will automatically take place at round restart. -timeout 300 \ No newline at end of file diff --git a/legacy/V2/Update to PR.bat b/legacy/V2/Update to PR.bat deleted file mode 100644 index a9ab59e02e..0000000000 --- a/legacy/V2/Update to PR.bat +++ /dev/null @@ -1,121 +0,0 @@ -@echo off -title Server Updater -SETLOCAL ENABLEDELAYEDEXPANSION -set HOME = %USERPROFILE% - -call config.bat -rem if the first arg to nudge.py is not a channel, it is treated as the "source" -if not defined UPDATE_LOG_CHANNEL set UPDATE_LOG_CHANNEL="UPDATER" -call bin\getcurdate.bat -call bin\findgit.bat -echo This will handle Resetting the repo downloading from git, merging the pr locally, compiling the server, and applying the PR test job. - -:PROMPT -SET /P UserInput=Please enter the pr number (without a # or anything of the sorts): -SET /A PR=UserInput - -if %PR% EQU %UserInput% ( - if %PR% GTR 0 ( - echo updating to pr %PR% - ) else ( - echo Bad input - goto PROMPT - ) -) else ( - echo Bad input - goto PROMPT -) -if exist updating.lk ( - echo ERROR: A current update script has been detected running. if you know this is a mistake: - pause - echo Please be double sure that an update script is not currently running, if you think one might be, close this window. otherwise: - pause -) - -echo lock>updating.lk - bin/activepr.bat -del /F /Q prtestjob.lk >nul 2>nul - -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "PR test job started. Updating master and merging PR #%PR% locally" >nul 2>nul - -call bin\updategit.bat -if %GIT_EXIT% neq 0 ( - echo git pull failed. Aborting update - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Git fetch failed. Aborting update" - del updating.lk >nul 2>nul - pause - exit /b 1 -) - -if defined PUSHCHANGELOGTOGIT ( - cd gitrepo - echo compiling change log - call python tools\ss13_genchangelog.py html/changelog.html html/changelogs - if !ERRORLEVEL! == 0 ( - echo pushing compiled changelog to server - git add -u html/changelog.html - git add -u html/changelogs - git commit -m "Automatic changelog compile, [ci skip]" - if !ERRORLEVEL! == 0 ( - git push - ) - REM an error here generally means there was nothing to commit. - ) - cd .. -) - -cd gitrepo -git fetch -f origin pull/%PR%/head:pr-%PR% -git merge pr-%PR% -if %ERRORLEVEL% neq 0 ( - git reset --hard - git clean -fd - cd .. - echo git merge of PR #%PR% failed. Aborting test merge. - echo An update was successfully preformed but not applied, if you want you can apply it by running recompile.bat - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "git merge of PR #%PR% failed. Aborting PR test job" >nul 2>nul - del updating.lk >nul 2>nul - pause - exit /b 1 -) -cd .. - -echo %PR%>prtestjob.lk -> bin/activepr.bat - -echo ################################## -echo ################################## -echo: -echo Updating to pr done, compiling in 10 seconds. If you want to preform other actions (like more test merges) You can close this now and do them. -echo: -del updating.lk >nul 2>nul - -timeout 10 - -echo lock>updating.lk -call bin\findab.bat - -call bin\copyfromgit.bat - - -echo compiling change log (again) -cd gamecode\%AB% -call python tools\ss13_genchangelog.py html/changelog.html html/changelogs -cd ..\.. - -echo Compiling game. -call bin\build.bat -if %DM_EXIT% neq 0 ( - echo DM compile failed. Aborting. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "DM compile failed. Aborting test merge." >nul 2>nul - del /F /Q updating.lk >nul 2>nul - pause - exit /b 1 -) - -del updating.lk >nul 2>nul -rmdir /q gamefolder -mklink /d gamefolder gamecode\%AB% >nul -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Test merge job finished. Test merge will take place next round." >nul 2>nul -echo Done. The test merge will automatically take place at round restart. -timeout 300 diff --git a/legacy/V2/Update without resetting.bat b/legacy/V2/Update without resetting.bat deleted file mode 100644 index 9a08573fb1..0000000000 --- a/legacy/V2/Update without resetting.bat +++ /dev/null @@ -1,100 +0,0 @@ -@echo off -@title Server Updater -SETLOCAL ENABLEDELAYEDEXPANSION -set HOME = %USERPROFILE% - -call config.bat -call bin\getcurdate.bat -call bin\findgit.bat - -echo This will update the server without resetting local changes like test merges. -echo Note: This doesn't update the changelog like a normal update does. -echo Ready? - -timeout 120 - -if exist updating.lk ( - echo ERROR: A current update script has been detected running. if you know this is a mistake: - pause - echo Please be double sure that an update script is not currently running, if you think one might be, close this window. otherwise: - pause -) - -echo lock>updating.lk - -rem if the first arg to nudge.py is not a channel, it is treated as the "source" -if not defined UPDATE_LOG_CHANNEL set UPDATE_LOG_CHANNEL="UPDATER" - -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Update job started (No reset mode)" >nul 2>nul - -cd gitrepo -git fetch origin -if %ERRORLEVEL% neq 0 ( - cd .. - echo git fetch failed. Aborting update. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Git fetch failed. Aborting update" - del updating.lk >nul 2>nul - pause - exit /b 1 -) -git merge origin/%REPO_BRANCH% -if %ERRORLEVEL% neq 0 ( - cd .. - echo git merge of upstream master failed, aborting update. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "git merge of upstream master failed, aborting update." >nul 2>nul - cd gitrepo - git merge --abort - if %ERRORLEVEL% neq 0 ( - echo ERROR: Error aborting update, resetting repo. - cd .. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Error aborting merge, Resetting git repo" >nul 2>nul - cd gitrepo - git reset --hard - git clean -fd - bin/activepr.bat - del /F /Q prtestjob.lk >nul 2>nul - echo NOTICE: We had to reset the repo's state, all active test merges were undone. - ) - cd .. - del updating.lk >nul 2>nul - pause - exit /b 1 -) -cd .. - - -echo ################################## -echo ################################## -echo: -echo In place update done, compiling in 10 seconds. If you want to preform other actions (like more test merges) You can close this now and do them. -echo: -del updating.lk >nul 2>nul - -timeout 10 - -echo lock>updating.lk -call bin\findab.bat - -call bin\copyfromgit.bat - -echo compiling change log -cd gamecode\%AB% -call python tools\ss13_genchangelog.py html/changelog.html html/changelogs -cd ..\.. - -echo Compiling game. -call bin\build.bat -if %DM_EXIT% neq 0 ( - echo DM compile failed. Aborting. - call python bot\nudge.py %UPDATE_LOG_CHANNEL% "DM compile failed Aborting update." >nul 2>nul - del /F /Q updating.lk >nul 2>nul - pause - exit /b 1 -) - -del updating.lk >nul 2>nul -rmdir /q gamefolder -mklink /d gamefolder gamecode\%AB% >nul -call python bot\nudge.py %UPDATE_LOG_CHANNEL% "Update job finished. Update will take place next round." >nul 2>nul -echo Done. The update will automatically take place at round restart. -timeout 300 diff --git a/legacy/V2/bin/activepr.bat b/legacy/V2/bin/activepr.bat deleted file mode 100644 index 554142925d..0000000000 --- a/legacy/V2/bin/activepr.bat +++ /dev/null @@ -1 +0,0 @@ -set PR= \ No newline at end of file diff --git a/legacy/V2/bin/build.bat b/legacy/V2/bin/build.bat deleted file mode 100644 index e231c6b920..0000000000 --- a/legacy/V2/bin/build.bat +++ /dev/null @@ -1,25 +0,0 @@ -call config.bat -call bin/findbyond.bat -set DME_FOLDER=gamefolder\ -if defined AB set DME_FOLDER=gamecode\%AB%\ - -set DME_LOCATION=%DME_FOLDER%%PROJECTNAME%.dme -set MDME_LOCATION=%DME_FOLDER%%PROJECTNAME%.mdme - -@del %MDME_LOCATION% >nul 2>nul -if not defined MAPFILE goto BUILD - -echo #define MAP_OVERRIDE >>%MDME_LOCATION% -echo #include "_maps\%MAPFILE%.dm" >>%MDME_LOCATION% - -:BUILD -echo #define SERVERTOOLS 1 >>%MDME_LOCATION% -echo #define PUTONHUB 1 >>%MDME_LOCATION% -type %DME_LOCATION% >>%MDME_LOCATION% - -dm -clean %MDME_LOCATION% -set DM_EXIT=%ERRORLEVEL% -@del %DME_FOLDER%%PROJECTNAME%.dmb >nul 2>nul -@del %DME_FOLDER%%PROJECTNAME%.rsc >nul 2>nul -@move %DME_FOLDER%%PROJECTNAME%.mdme.dmb %DME_FOLDER%%PROJECTNAME%.dmb >nul 2>nul -@move %DME_FOLDER%%PROJECTNAME%.mdme.rsc %DME_FOLDER%%PROJECTNAME%.rsc >nul 2>nul diff --git a/legacy/V2/bin/copyfromgit.bat b/legacy/V2/bin/copyfromgit.bat deleted file mode 100644 index acb9114902..0000000000 --- a/legacy/V2/bin/copyfromgit.bat +++ /dev/null @@ -1,21 +0,0 @@ -echo Removing old files -rem delete the symlinks manually to ensure their targets don't get recursively deleted -rmdir /q gamecode\%AB%\data >nul 2>nul -rmdir /q gamecode\%AB%\config >nul 2>nul -del /q gamecode\%AB%\nudge.py >nul 2>nul -del /q gamecode\%AB%\libmysql.dll >nul 2>nul - -del /S /F /Q gamecode\%AB%\ >nul 2>nul - -echo Copying files -xcopy gitrepo gamecode\%AB% /Y /X /K /R /H /I /C /V /E /Q /EXCLUDE:copyexclude.txt >nul -mkdir gamecode\%AB%\.git\logs\refs\remotes\origin -copy gitrepo\.git\logs\HEAD gamecode\%AB%\.git\logs\HEAD /D /V /Y >nul -copy gitrepo\.git\logs\refs\remotes\origin\master gamecode\%AB%\.git\logs\refs\remotes\origin\master /D /V /Y >nul - -mklink gamecode\%AB%\nudge.py ..\..\bot\nudge.py >nul -rmdir /q gamecode\%AB%\data >nul 2>nul -rmdir /s /q gamecode\%AB%\data >nul 2>nul -mklink /d gamecode\%AB%\data ..\..\gamedata\data >nul -mklink /d gamecode\%AB%\config ..\..\gamedata\config >nul -mklink gamecode\%AB%\libmysql.dll ..\..\gamedata\libmysql.dll >nul diff --git a/legacy/V2/bin/findab.bat b/legacy/V2/bin/findab.bat deleted file mode 100644 index 0c24a7571b..0000000000 --- a/legacy/V2/bin/findab.bat +++ /dev/null @@ -1,45 +0,0 @@ -del gamecode\a\updater.temp >nul 2>nul -del gamecode\b\updater.temp >nul 2>nul - -echo test >gamefolder\updater.temp - -if exist gamefolder\%PROJECTNAME%.rsc.lk ( - rem we attempt to delete the lock file to see if the server is currently running. - del /q gamefolder\%PROJECTNAME%.rsc.lk >nul 2>nul - if exist gamefolder\%PROJECTNAME%.rsc.lk set RUNNING=1 -) - -if exist gamecode\a\updater.temp ( - if defined RUNNING ( - echo Current folder detected to be the "A" folder. Game is currently running. Updating to the "B" folder. - set AB=b - ) else ( - echo Current folder detected to be the "A" folder. Game is not currently running, Updating to the "A" folder. - if exist gamecode\b\%PROJECTNAME%.rsc.lk ( - rem we attempt to delete the lock file to see if the server is currently running. - del /q gamecode\b\%PROJECTNAME%.rsc.lk >nul 2>nul - if exist gamecode\b\%PROJECTNAME%.rsc.lk set RUNNING=1 - rmdir /q gamefolder - mklink /d gamefolder gamecode\b >nul - echo Game is in actually currently running on the "B" folder, Resetting current folder to the "B" folder first - ) - set AB=a - ) -) else if exist gamecode\b\updater.temp ( - if defined RUNNING ( - echo Current folder detected to be the "B" folder. Game is currently running, Updating to the "A" folder. - set AB=a - ) else ( - echo Current folder detected to be the "B" folder. Game is not currently running, Updating to the "B" folder. - if exist gamecode\a\%PROJECTNAME%.rsc.lk ( - rem we attempt to delete the lock file to see if the server is currently running. - del /q gamecode\a\%PROJECTNAME%.rsc.lk >nul 2>nul - if exist gamecode\a\%PROJECTNAME%.rsc.lk set RUNNING=1 - rmdir /q gamefolder - mklink /d gamefolder gamecode\a >nul - echo Game is in actually currently running on the "A" folder, Resetting current folder to the "A" folder first - ) - set AB=b - ) -) -del gamefolder\updater.temp >nul 2>nul \ No newline at end of file diff --git a/legacy/V2/bin/findbyond.bat b/legacy/V2/bin/findbyond.bat deleted file mode 100644 index ff07af2dd6..0000000000 --- a/legacy/V2/bin/findbyond.bat +++ /dev/null @@ -1,26 +0,0 @@ - -@dm.exe -h >nul 2>nul -IF %ERRORLEVEL% NEQ 9009 ( - goto :eof -) - -set PATH=%PATH%;%BYOND_LOCATION_PATH% -@dm.exe -h >nul 2>nul -IF %ERRORLEVEL% NEQ 9009 ( - goto :eof -) - -@"c:\Program Files (x86)\BYOND\bin\dm.exe" -h >nul 2>nul -IF %ERRORLEVEL% NEQ 9009 ( - set "PATH=%PATH%;c:\Program Files (x86)\BYOND\bin\" - goto :eof -) -@"c:\Program Files\BYOND\bin\dm.exe" -h >nul 2>nul -IF %ERRORLEVEL% NEQ 9009 ( - set "PATH=%PATH%;c:\Program Files\BYOND\bin\" - goto :eof -) - -echo byond not found. Aborting. If byond is installed, set the GIT_LOCATION variable inside config.bat -timeout 60 -exit 11 \ No newline at end of file diff --git a/legacy/V2/bin/findgit.bat b/legacy/V2/bin/findgit.bat deleted file mode 100644 index 9c802b54c6..0000000000 --- a/legacy/V2/bin/findgit.bat +++ /dev/null @@ -1,31 +0,0 @@ -REM check if git is already in path -git --version >nul 2>nul && goto :eof - -REM now lets try our override. -set PATH=%PATH%;%GIT_LOCATION_PATH% -@git --version >nul 2>nul && goto :eof - -REM credit to sschuberth@http://stackoverflow.com/questions/8507368/finding-the-path-where-git-is-installed-on-a-windows-system -REM Read the Git for Windows installation path from the Registry. - -:REG_QUERY -for /f "skip=2 delims=: tokens=1*" %%a in ('reg query "HKLM\SOFTWARE%WOW%\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1" /v InstallLocation 2^> nul') do ( - for /f "tokens=3" %%z in ("%%a") do ( - set GIT=%%z:%%b - ) -) -if "%GIT%"=="" ( - if "%WOW%"=="" ( - rem Attempt to find it on the 32bit register section - set WOW=\Wow6432Node - goto REG_QUERY - ) -) - -set PATH=%GIT%bin;%PATH% - -@git --version >nul 2>nul && goto :eof - -echo Git not found. Aborting. If git is installed, set the GIT_LOCATION variable inside config.bat -timeout 60 -exit 10 \ No newline at end of file diff --git a/legacy/V2/bin/getcurdate.bat b/legacy/V2/bin/getcurdate.bat deleted file mode 100644 index f5360756ce..0000000000 --- a/legacy/V2/bin/getcurdate.bat +++ /dev/null @@ -1,2 +0,0 @@ -FOR /f %%a in ('WMIC OS GET LocalDateTime ^| find "."') DO set DTS=%%a -set CUR_DATE=%DTS:~0,4%-%DTS:~4,2%-%DTS:~6,2% \ No newline at end of file diff --git a/legacy/V2/bin/getunixtime.bat b/legacy/V2/bin/getunixtime.bat deleted file mode 100644 index 2b4115faab..0000000000 --- a/legacy/V2/bin/getunixtime.bat +++ /dev/null @@ -1,12 +0,0 @@ -REM setlocal -call :GetUnixTime UNIXTIME -goto :EOF - -:GetUnixTime -REM setlocal enableextensions -for /f %%x in ('wmic path win32_utctime get /format:list ^| findstr "="') do ( - set %%x) -set /a z=(14-100%Month%%%100)/12, y=10000%Year%%%10000-z -set /a ut=y*365+y/4-y/100+y/400+(153*(100%Month%%%100+12*z-3)+2)/5+Day-719469 -set /a ut=ut*86400+100%Hour%%%100*3600+100%Minute%%%100*60+100%Second%%%100 -endlocal & set "%1=%ut%" & goto :EOF \ No newline at end of file diff --git a/legacy/V2/bin/unixtime.vbs b/legacy/V2/bin/unixtime.vbs deleted file mode 100644 index f5e1ae78f0..0000000000 --- a/legacy/V2/bin/unixtime.vbs +++ /dev/null @@ -1 +0,0 @@ -WScript.Echo DateDiff("s", "01/01/1970 00:00:00", Now()) \ No newline at end of file diff --git a/legacy/V2/bin/updategit.bat b/legacy/V2/bin/updategit.bat deleted file mode 100644 index e16cfc3922..0000000000 --- a/legacy/V2/bin/updategit.bat +++ /dev/null @@ -1,12 +0,0 @@ -call bin\findgit.bat -echo Updating repo - -cd gitrepo - -git branch backup-%CUR_DATE% >nul 2>nul -git fetch -set GIT_EXIT=%ERRORLEVEL% - -git reset origin/%REPO_BRANCH% --hard - -cd .. \ No newline at end of file diff --git a/legacy/V2/config.bat b/legacy/V2/config.bat deleted file mode 100644 index eb3ecd13e7..0000000000 --- a/legacy/V2/config.bat +++ /dev/null @@ -1,42 +0,0 @@ -@echo off -REM Server Tools configuration file. Lines starting with rem are comments and ignored. - -REM This must be set to the name of your dme without the .dme part. (should be fine leaving this alone unless you renamed the code) -set PROJECTNAME=tgstation - - -REM location of the repo. (use an ssh url if you plan to push compiled changlogs) -REM Only set during install, do not re-run install.bat if you change this, instead manually change the remote of the gitrepo folder using git tools -set REPO_URL=https://github.com/tgstation/-tg-station.git - - -REM What branch of the repo to use. -set REPO_BRANCH=master - - -REM Override Map (This disables map roation and forces the selected map to always be loaded) -REM set MAPFILE=tgstation2 -REM set MAPFILE=metastation - - -REM port to use when starting the server -set PORT=2337 - - -REM This is the channel to log updates to. Leave blank to log to the bot's default logging channel (this is done via the tgstation minibot bot, optional) -set UPDATE_LOG_CHANNEL=#devbus,#coderbus,#tgstation13 - - -REM Attempt to push the compiled changelog to the configured git server? (set to anything) -REM This requires you configure git with authentication for the upstream server. (git for windows users should just put an ssh key in c:\users\USERNAME_HERE\.ssh\ as the filename id_rsa) And you should have installed this with a git, ssh, or file url) -set PUSHCHANGELOGTOGIT= - - -REM location of git. The script will attempt to auto detect this, but if it fails, you can set it manually. -REM github for windows users see http://www.chambaud.com/2013/07/08/adding-git-to-path-when-using-github-for-windows/ (an example is provided below) - -set GIT_LOCATION_PATH= -REM set GIT_LOCATION_PATH=C:\Users\Administrator\AppData\Local\GitHub\PortableGit_c2ba306e536fdf878271f7fe636a147ff37326ad\bin;C:\Users\Administrator\AppData\Local\GitHub\PortableGit_c2ba306e536fdf878271f7fe636a147ff37326ad\cmd - -REM path to the byond bin folder. (same rules as git path above, almost always auto detected, but you could point this to the output of the zip version for install-less setups, and even abuse that to make updating byond versions less of a pain by just changing this config and then crashing the server.) -set BYOND_LOCATION_PATH= diff --git a/legacy/V2/copyexclude.txt b/legacy/V2/copyexclude.txt deleted file mode 100644 index 3cf4643303..0000000000 --- a/legacy/V2/copyexclude.txt +++ /dev/null @@ -1,9 +0,0 @@ -//This is a list of files to exclude from copying. See xcopy's documentation for /exclude for more info. -// - -gitrepo\config\ -gitrepo\data\ -gitrepo\bot\ -gitrepo\.git\ -gitrepo\libmysql.dll - diff --git a/legacy/V2/disable crash dialog.reg b/legacy/V2/disable crash dialog.reg deleted file mode 100644 index d1c477a445..0000000000 --- a/legacy/V2/disable crash dialog.reg +++ /dev/null @@ -1,4 +0,0 @@ -Windows Registry Editor Version 5.00 - -[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting] -"DontShowUI"=dword:00000001 diff --git a/legacy/V2/install.bat b/legacy/V2/install.bat deleted file mode 100644 index c00e9d3849..0000000000 --- a/legacy/V2/install.bat +++ /dev/null @@ -1,81 +0,0 @@ -@echo off -@title Server Tools Installer. -set HOME = %USERPROFILE% -call config.bat - -echo This will download the game code from git and install the all the files and folders and symbolic links needed to use the server tools in to the current directory. - -echo This requires git be installed. - -echo Once this is done, you can safely delete this file if you wish. - -echo Ready? -pause - -call bin/findgit.bat - -echo Downloading repo.... -git clone %REPO_URL% gitrepo -IF %ERRORLEVEL% NEQ 0 ( - echo git clone failed. aborting. - pause - goto ABORT -) -cd gitrepo -git checkout %REPO_BRANCH% -cd .. - -echo Repo downloaded. -echo Setting up folders... -mkdir gamecode\a -mkdir gamecode\b -mkdir gamedata -mkdir bot - -echo Copying things around.... -echo (1/3) -xcopy gitrepo\data gamedata\data /Y /X /K /R /H /I /C /V /E /Q >nul -xcopy gitrepo\config gamedata\config /Y /X /K /R /H /I /C /V /E /Q >nul -xcopy gitrepo\tools\minibot bot /Y /X /K /R /H /I /C /V /E /Q >nul -copy gitrepo\libmysql.dll gamedata\libmysql.dll /D /V /Y >nul -echo (2/3) -xcopy gitrepo gamecode\a /Y /X /K /R /H /I /C /V /E /Q /EXCLUDE:copyexclude.txt >nul -mkdir gamecode\a\.git\logs\ -copy gitrepo\.git\logs\HEAD gamecode\a\.git\logs\HEAD /D /V /Y >nul -echo (3/3) -xcopy gitrepo gamecode\b /Y /X /K /R /H /I /C /V /E /Q /EXCLUDE:copyexclude.txt >nul -mkdir gamecode\b\.git\logs >nul -copy gitrepo\.git\logs\HEAD gamecode\b\.git\logs\HEAD /D /V /Y >nul -echo done. - -echo Setting up symbolic links. -mklink gamecode\a\nudge.py ..\..\bot\nudge.py -mklink gamecode\a\libmysql.dll ..\..\gamedata\libmysql.dll -mklink /d gamecode\a\data ..\..\gamedata\data -mklink /d gamecode\a\config ..\..\gamedata\config - -mklink gamecode\b\nudge.py ..\..\bot\nudge.py -mklink gamecode\b\libmysql.dll ..\..\gamedata\libmysql.dll -mklink /d gamecode\b\data ..\..\gamedata\data -mklink /d gamecode\b\config ..\..\gamedata\config - -mklink /d gamefolder gamecode\a - -echo prepping python (if installed) -pip install PyYaml -pip install beautifulsoup4 - -echo Compiling for the first time. - -echo Compiling change log. -cd gamefolder -call python tools\ss13_genchangelog.py html/changelog.html html/changelogs -cd .. -echo Compiling game. -call bin\build.bat -if %DM_EXIT% neq 0 echo DM compile failed. - -echo Done. You may start the server using the start server program or change the game config in gamedata\config -pause - -:ABORT diff --git a/legacy/V3/.gitignore b/legacy/V3/.gitignore deleted file mode 100644 index 90b3b65a9c..0000000000 --- a/legacy/V3/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -#Visual studio stuff -.vscode/* -.vs/* - -#VS stuff - -*/bin/* -*/obj/* -*/Debug/* -*/Release/* - -packages/* -*.user -*.dmb -*.int -/TestResults diff --git a/legacy/V3/AssemblyInfo.global.cs b/legacy/V3/AssemblyInfo.global.cs deleted file mode 100644 index 13a8d67ebd..0000000000 --- a/legacy/V3/AssemblyInfo.global.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -//You cannot one definition the version number -//Believe me, I've tried, the compiler hates it so much -[assembly: AssemblyVersion("3.2.2.0")] -[assembly: AssemblyFileVersion("3.2.2.0")] -[assembly: AssemblyInformationalVersion("3.2.2.0")] - -[assembly: InternalsVisibleTo("TGS.Tests")] diff --git a/legacy/V3/DMAPI/server_tools.dm b/legacy/V3/DMAPI/server_tools.dm deleted file mode 100644 index 444d6be8b4..0000000000 --- a/legacy/V3/DMAPI/server_tools.dm +++ /dev/null @@ -1,129 +0,0 @@ -// /tg/station 13 server tools API -#define SERVICE_API_VERSION_STRING "3.2.0.3" - -//CONFIGURATION -//use this define if you want to do configuration outside of this file -#ifndef SERVER_TOOLS_EXTERNAL_CONFIGURATION -//Comment this out once you've filled in the below -#error /tg/station server tools interface unconfigured - -//Required interfaces (fill in with your codebase equivalent): - -//create a global variable named `Name` and set it to `Value` -//These globals must not be modifiable from anywhere outside of the server tools -#define SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(Name, Value) -//Read the value in the global variable `Name` -#define SERVER_TOOLS_READ_GLOBAL(Name) -//Set the value in the global variable `Name` to `Value` -#define SERVER_TOOLS_WRITE_GLOBAL(Name, Value) -//display an announcement `message` from the server to all players -#define SERVER_TOOLS_WORLD_ANNOUNCE(message) -//Write a string `message` to a server log -#define SERVER_TOOLS_LOG(message) -//Notify current in-game administrators of a string `event` -#define SERVER_TOOLS_NOTIFY_ADMINS(event) -//The current amount of connected clients -#define SERVER_TOOLS_CLIENT_COUNT -#endif - -//Required hooks: - -//Put this somewhere in /world/New() that is always run -#define SERVER_TOOLS_ON_NEW ServiceInit() -//Put this somewhere in /world/Topic(T, Addr, Master, Keys) that is always run before T is modified -#define SERVER_TOOLS_ON_TOPIC var/service_topic_return = ServiceCommand(params2list(T)); if(service_topic_return) return service_topic_return -//Put at the beginning of world/Reboot(reason) -#define SERVER_TOOLS_ON_REBOOT ServiceReboot() - -//Optional callable functions: - -//Returns the string version of the API -#define SERVER_TOOLS_API_VERSION ServiceAPIVersion() -//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise -//No function below this succeed if this is FALSE -#define SERVER_TOOLS_PRESENT RunningService() -//Gets the current version of the service running the server -#define SERVER_TOOLS_VERSION ServiceVersion() -//Forces a hard reboot of BYOND by ending the process -//unlike del(world) clients will try to reconnect -//If the service has not requested a shutdown, the world will reboot shortly after -#define SERVER_TOOLS_REBOOT_BYOND(silent) world.ServiceEndProcess(silent) -/* - Gets the list of any testmerged github pull requests - - "[PR Number]" => list( - "title" -> PR title - "commit" -> Full hash of commit merged - "author" -> Github username of the author of the PR - ) -*/ -#define SERVER_TOOLS_PR_LIST GetTestMerges() -//Sends a message to connected game chats -#define SERVER_TOOLS_CHAT_BROADCAST(message) world.ChatBroadcast(message) -//Sends a message to connected admin chats -#define SERVER_TOOLS_RELAY_BROADCAST(message) world.AdminBroadcast(message) - -//IMPLEMENTATION - -#define REBOOT_MODE_NORMAL 0 -#define REBOOT_MODE_HARD 1 -#define REBOOT_MODE_SHUTDOWN 2 - -#define SERVICE_WORLD_PARAM "server_service" -#define SERVICE_VERSION_PARAM "server_service_version" -#define SERVICE_INSTANCE_PARAM "server_instance" -#define SERVICE_PR_TEST_JSON "prtestjob.json" -#define SERVICE_INTERFACE_DLL "TGDreamDaemonBridge.dll" -#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint" - -#define SERVICE_CMD_HARD_REBOOT "hard_reboot" -#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown" -#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce" -#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands" -#define SERVICE_CMD_API_COMPATIBLE "api_compat" -#define SERVICE_CMD_PLAYER_COUNT "client_count" - -#define SERVICE_CMD_PARAM_KEY "serviceCommsKey" -#define SERVICE_CMD_PARAM_COMMAND "command" -#define SERVICE_CMD_PARAM_SENDER "sender" -#define SERVICE_CMD_PARAM_CUSTOM "custom" - -#define SERVICE_JSON_PARAM_HELPTEXT "help_text" -#define SERVICE_JSON_PARAM_ADMINONLY "admin_only" -#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters" - -#define SERVICE_REQUEST_KILL_PROCESS "killme" -#define SERVICE_REQUEST_KILL_PROCESS_SILENT "killmesilent" -#define SERVICE_REQUEST_IRC_BROADCAST "irc" -#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc" -#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot" -#define SERVICE_REQUEST_API_VERSION "api_ver" - -#define SERVICE_RETURN_SUCCESS "SUCCESS" - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ diff --git a/legacy/V3/DMAPI/st_commands.dm b/legacy/V3/DMAPI/st_commands.dm deleted file mode 100644 index ce3fb1bb75..0000000000 --- a/legacy/V3/DMAPI/st_commands.dm +++ /dev/null @@ -1,76 +0,0 @@ -/datum/server_tools_command - var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command - var/help_text = "" //help text for this command - var/required_parameters = 0 //number of parameters required for this command - var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins - -//override to implement command -//sender is the display name of who sent the command -//params is the trimmed string following the command name -/datum/server_tools_command/proc/Run(sender, params) - CRASH("[type] has no implementation for Run()") - -/world/proc/ListServiceCustomCommands(warnings_only) - if(!warnings_only) - . = list() - var/list/command_name_types = list() - var/list/warned_command_names = warnings_only ? list() : null - for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command) - var/datum/server_tools_command/stc = I - var/command_name = initial(stc.name) - var/static/list/warned_server_tools_names = list() - if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) - if(warnings_only && !warned_command_names[command_name]) - SERVER_TOOLS_LOG("WARNING: Custom command [command_name] can't be used as it is empty or contains illegal characters!") - warned_command_names[command_name] = TRUE - continue - - if(command_name_types[command_name]) - if(warnings_only) - SERVER_TOOLS_LOG("WARNING: Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!") - continue - command_name_types[stc] = command_name - - if(!warnings_only) - .[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = initial(stc.required_parameters)) - -/world/proc/HandleServiceCustomCommand(command, sender, params) - var/static/list/cached_custom_server_tools_commands - if(!cached_custom_server_tools_commands) - cached_custom_server_tools_commands = list() - for(var/I in typesof(/datum/server_tools_command) - /datum/server_tools_command) - var/datum/server_tools_command/stc = I - cached_custom_server_tools_commands[lowertext(initial(stc.name))] = stc - - var/command_type = cached_custom_server_tools_commands[command] - if(!command_type) - return FALSE - var/datum/server_tools_command/stc = new command_type - return stc.Run(sender, params) || TRUE - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ diff --git a/legacy/V3/DMAPI/st_interface.dm b/legacy/V3/DMAPI/st_interface.dm deleted file mode 100644 index 931ab10ff3..0000000000 --- a/legacy/V3/DMAPI/st_interface.dm +++ /dev/null @@ -1,135 +0,0 @@ -SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(reboot_mode, REBOOT_MODE_NORMAL) -SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(server_tools_api_compatible, FALSE) - -/proc/GetTestMerges() - if(RunningService(TRUE) && fexists(SERVICE_PR_TEST_JSON)) - . = json_decode(file2text(SERVICE_PR_TEST_JSON)) - if(.) - return - return list() - -/world/proc/ServiceInit() - if(!RunningService(TRUE)) - return - ListServiceCustomCommands(TRUE) - ExportService("[SERVICE_REQUEST_API_VERSION] [SERVER_TOOLS_API_VERSION]", TRUE) - -/proc/RunningService(skip_compat_check = FALSE) - if(!skip_compat_check && !SERVER_TOOLS_READ_GLOBAL(server_tools_api_compatible)) - return FALSE - . = world.params[SERVICE_WORLD_PARAM] != null - if(. && world.system_type != MS_WINDOWS) - SERVER_TOOLS_LOG("Warning: Server tools world parameter detected but not running on Windows. Aborting initialization!") - return FALSE - -/proc/ServiceVersion() - if(RunningService(TRUE)) - return world.params[SERVICE_VERSION_PARAM] - -/proc/ServiceAPIVersion() - return SERVICE_API_VERSION_STRING - -/world/proc/ExportService(command, skip_compat_check = FALSE) - . = FALSE - if(!RunningService(skip_compat_check)) - return - if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL)) - CRASH("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.") - var/instance = params[SERVICE_INSTANCE_PARAM] - if(!instance) - instance = "TG Station Server" //maybe just upgraded - call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance, command) //trust no retval - return TRUE - -/world/proc/ChatBroadcast(message) - ExportService("[SERVICE_REQUEST_IRC_BROADCAST] [message]") - -/world/proc/AdminBroadcast(message) - ExportService("[SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE] [message]") - -/world/proc/ServiceEndProcess(silent = FALSE) - SERVER_TOOLS_LOG("Sending shutdown request!"); - sleep(world.tick_lag) //flush the buffers - ExportService(silent ? SERVICE_REQUEST_KILL_PROCESS_SILENT : SERVICE_REQUEST_KILL_PROCESS) - -//called at the exact moment the world is supposed to reboot -/world/proc/ServiceReboot() - switch(SERVER_TOOLS_READ_GLOBAL(reboot_mode)) - if(REBOOT_MODE_HARD) - SERVER_TOOLS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...") - ServiceEndProcess() - if(REBOOT_MODE_SHUTDOWN) - SERVER_TOOLS_WORLD_ANNOUNCE("The server is shutting down...") - ServiceEndProcess() - else - ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know - -/world/proc/ServiceCommand(list/params) - var/their_sCK = params[SERVICE_CMD_PARAM_KEY] - if(!their_sCK || !RunningService(TRUE)) - return FALSE //continue world/Topic - - var/sCK = world.params[SERVICE_WORLD_PARAM] - if(their_sCK != sCK) - return "Invalid comms key!"; - - var/command = params[SERVICE_CMD_PARAM_COMMAND] - if(!command) - return "No command!" - - switch(command) - if(SERVICE_CMD_API_COMPATIBLE) - SERVER_TOOLS_WRITE_GLOBAL(server_tools_api_compatible, TRUE) - return SERVICE_RETURN_SUCCESS - if(SERVICE_CMD_HARD_REBOOT) - if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_HARD) - SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_HARD) - SERVER_TOOLS_LOG("Hard reboot requested by service") - SERVER_TOOLS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by service.") - if(SERVICE_CMD_GRACEFUL_SHUTDOWN) - if(SERVER_TOOLS_READ_GLOBAL(reboot_mode) != REBOOT_MODE_SHUTDOWN) - SERVER_TOOLS_WRITE_GLOBAL(reboot_mode, REBOOT_MODE_SHUTDOWN) - SERVER_TOOLS_LOG("Shutdown requested by service") - message_admins("The world will shutdown at the end of the game. Requested by service.") - if(SERVICE_CMD_WORLD_ANNOUNCE) - var/msg = params["message"] - if(!istext(msg) || !msg) - return "No message set!" - SERVER_TOOLS_WORLD_ANNOUNCE(msg) - return SERVICE_RETURN_SUCCESS - if(SERVICE_CMD_PLAYER_COUNT) - return "[SERVER_TOOLS_CLIENT_COUNT]" - if(SERVICE_CMD_LIST_CUSTOM) - return json_encode(ListServiceCustomCommands(FALSE)) - else - var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM]) - if(custom_command_result) - return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS - return "Unknown command: [command]" - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ diff --git a/legacy/V3/README.md b/legacy/V3/README.md deleted file mode 100644 index fd2e6d2d93..0000000000 --- a/legacy/V3/README.md +++ /dev/null @@ -1,240 +0,0 @@ -# Tgstation Toolkit: - -[![Build status](https://ci.appveyor.com/api/projects/status/7t1h7bvuha0p9j5f/branch/master?svg=true)](https://ci.appveyor.com/project/Cyberboss/tgstation-server-tools/branch/master) [![Build Status](https://travis-ci.org/tgstation/tgstation-server.svg?branch=master)](https://travis-ci.org/tgstation/tgstation-server) [![codecov](https://codecov.io/gh/tgstation/tgstation-server/branch/master/graph/badge.svg)](https://codecov.io/gh/tgstation/tgstation-server) [![Waffle.io - Columns and their card count](https://badge.waffle.io/tgstation/tgstation-server.png?columns=all)](https://waffle.io/tgstation/tgstation-server?utm_source=badge) - -[![GitHub license](https://img.shields.io/github/license/tgstation/tgstation-server.svg)](https://github.com/tgstation/tgstation-server/blob/master/LICENSE) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tgstation/tgstation-server.svg)](http://isitmaintained.com/project/tgstation/tgstation-server "Average time to resolve an issue") [![NuGet version](https://img.shields.io/nuget/v/TGServiceInterface.svg)](https://www.nuget.org/packages/TGServiceInterface) - -[![forthebadge](http://forthebadge.com/images/badges/made-with-c-sharp.svg)](http://forthebadge.com) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) - -[![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg)](http://forthebadge.com) - - -This is a toolset to manage a production server of /tg/Station13 (and its forks). It includes the ability to update the server without having to stop or shutdown the server (the update will take effect next round) the ability start the server and restart it if it crashes, as well as systems for fixing errors and merging GitHub Pull Requests locally. - -Generally, updates force a live tracking of the configured git repo, resetting local modifications. If you plan to make modifications, set up a new git repo to store your version of the code in, and point this script to that in the config (explained below). This can be on github or a local repo using file:/// urls. - -Requires python 2.7/3.6 to be installed for changelog generation - -### Legacy Server -* The old cmd script server can be found in the legacy tree - - -## Installing -1. Either compile from source (requires .NET Framework 4.5.2, Nuget, and WiX toolset 4.0) or download the latest [release from github](https://github.com/tgstation/tgstation-server-tools/releases) -1. Unzip setup files -1. Run the installer or use the console server - -## Installing (GUI): -1. Launch `TGControlPanel.exe` as an administrator. A shortcut can be found on your desktop -1. Use the `Create Instance` button to create a new server instance -1. Go to the `Repository` Tab and set the remote address and branch of the git you with to track -1. Hit the clone button -1. While waiting go to the `BYOND` tab and install the BYOND version you wish -1. You may also configure an IRC and/or discord bot for the server on the chat tab -1. Once the clone is complete you may set up a committer identity, user name, and password on the `Repository` tab for pushing changelog updates -1. Go to the `Server` tab and click the `Initialize Game Folders` button -1. Optionally change the `Project Path` Setting from tgstation to wherever the dme/dmb pair are in your repository -1. Optionally tick the Autostart box if you wish to have your server start with Windows -1. When game folder initialization is complete, click the `Copy from Repo and Compile` option - -## Installing (CL example): -This process is identical to the above steps in command line mode. You can always learn more about a command using `?` i.e. `repo ?` -1. Launch TGCommandLine.exe as an administrator (running with no parameters puts you in interactive mode) -1. `service set-python-path C:\Python27` -1. `service create-instance "TGS" D:\tgstation` -1. `instance` And enter `TGS`. If you aren't using interactive mode, the following commands must be suffixed with `--instance TGS` -1. `repo setup https://github.com/tgstation/tgstation master` -1. `byond update 511.1385` -1. `irc nick TGS3Test` -1. `irc set-auth-mode channel-mode` -1. `irc set-auth-level %` -1. `irc setup-auth NickServ "id hunter2"` Yes this is the real password, please use it only for testing -1. `irc join botbus dev` -1. `irc join botbus admin` -1. `irc join botbus wd` -1. `irc join botbus game` -1. `irc enable` -1. `discord set-token Rjfa93jlksjfj934jlkjasf8a08wfl.asdjfj08e44` See https://discordapp.com/developers/docs/topics/oauth2#bots -1. `discord set-auth-mode role-id` -1. `discord addmin 192837419273409` See how to get a role id: https://www.reddit.com/r/discordapp/comments/5bezg2/role_id/. Note that if you `discord set-auth-mode user-id` you'll need to use user ids (Enable developer mode, right click user, `Copy ID`) -1. `discord join 12341234453235 dev` This is a channel id (Enable developer mode, right click channel, `Copy ID`) -1. `discord join 34563456344245 dev` -1. `discord join 23452362574456 admin` -1. `discord join 23452362574456 wd` -1. `discord join 53457345736788 game` -1. `discord enable` -1. `repo set-name tgstation-server` These two lines specify the changelog's committer identity. They are not mirrored in the GUI -1. `repo set-email tgstation-server@tgstation13.org` -1. `repo set-credentials` And follow the prompts -1. `dm project-name tgstation` -1. `dd autostart on` -1. `repo status` To check the clone job status -1. `dm initialize --wait` -1. `dd start` - -## Setting up Multi-User - -1. Create windows accounts for those you wish to have access to the service -1. Join them in a common windows group -1. Run TGCommandLine.exe as an administrator -1. `admin set-group --instance ""` - -Note: Due to internal functionality, a user who has access to at least one server instance will be able to view the metadata (Name, path, Logging ID, and Enabled status) of all instances. - -## Setting up Remote access - -1. Obtain an SSL certificate to secure the connection (this is beyond the scope of this guide) -1. Either stick with the default port `38607` or change it with `admin set-port ` -1. [Bind the SSL certificate to the port](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate) - - e.g. `netsh http add sslcert ipport=0.0.0.0: certhash= appid={F32EDA25-0855-411C-AF5E-F0D042917E2D}` - - The `appid` GUID actually doesn't matter, but for sanity, you should use the GUID of TGServerService.exe as printed above - - Power shell users remember to quit the appid: `netsh http add sslcert ipport=0.0.0.0: certhash= appid="{F32EDA25-0855-411C-AF5E-F0D042917E2D}"` as {} has special meaning in powershell -1. Ensure the port can be acccessed from the internet -1. Log in from any computer using a username and password from the service computer in either the CLI or GUI - -## Updating - -The service supports updates while running a DreamDaemon instance. Simply install an updated version as you normally would and process ownership will transfer smoothly to the new service. - -### Folders and Files (None of these should be touched): -* `Game//` - * This will house two copies of the game code, one for updating and one for live. When updating, it will automatically swap them. - -* `Static/` - * This contains the `data/` and `config/` folders from the code. They are stored here and a symbolic link is created in the `Game` folders pointing to here. - * Resetting the repository will create a backup of this and reinitalize it - -* `Game/Live/` - * This is a symbolic link pointing to current "live" folder. - * When the server is updated, we just point this to the updating folder so that the update takes place next round. - -* `Diagnostics` - * This contains various timestamped diagnostic information for DreamDaemon invocations - -* `Repository/` - * This contains the actual git repository, all changes in here will be overwritten during update operations. - -* `RepoKey/` - * This contains ssh key information for automatic changelog pushing. - -* `EventHandlers/` - * This contains batch files that run after certain events, currently only precompile and postcompile events are implemented. If you'd like an event handler you should create a file in this folder with the following name: `{event_name}.bat` for example: if I want an event handler for precompile, I would name it `precompile.bat`. - -* `BYOND/` - * This contains the actual BYOND installation the server uses - -* `BYOND_staging/` - * This appears when a BYOND update is queued but can't currently be applied due to usage of the current BYOND version. It will be applied at the first possible moment. Restarting the service deletes this folder - -* `BYOND_revision.zip` - * This is a queued update downloaded from BYOND, it will be unzipped into BYOND_staging and deleted. Restarting the service deletes this file - -* `TGDreamDaemonBridge.dll` - * This is the .dll the TGS3 API `call()()`s into to RPC the server instance that runs it - * Instance -> DreamDaemon communication is achieved via `world/Topic()` - -* `prtestjob.json` - * This contains information about current test merged pull requests in the Repository folder - -* `TGS3.json` - * This is a copy of `TGS3.json` from the Repository. If a repostory change creates differences between the two, update operations will be blocked until the user confirms they want to change it - -* `Instance.json` - * The internal configuration settings for a server instance. Note that access to this file bypasses API user restrictions - -### Codebase integration -To get the TGS3 API for your code base, import the 3 .dm files in the `DMAPI` folder into your include structure, then fill out the configuration as documented in the comments of server_tools.dm. Then, you may want to add a `TGS3.json` file to specify any static directories and .dlls your codebase uses, along with the optional changelog compile options. Any changes to `TGS3.json` will block compiler operations until a server operator manually approves them. - -### Starting the game server: -To run the game server, open the `Server` tab of the control panel and click either `Start` - -It will restart the game server if it shutdowns for any reason, giving up after 5 tries in under a minute - -### Updating the server: -To update the server, open the `Server` tab of the control panel. Click `Update Server`. (it will git pull, compile, all that jazz). Note that this button won't clear test merges. - -(Note: Updating automatically does a code reset, clearing ALL changes to the local git repo, including manual changes (This will not change any data in the `Static/` folder)) - -Updates do not require the server to be shutdown, changes will apply next round if the server is currently running. - -All DM compilation will log to the server what commit they happened at and create a backup tag in the local repository. - -### Locally merge GitHub Pull Requests (PR test merge): -This feature currently only works if github is the remote (git server). - -Running these will merge the pull request then recompile the server, changes take effect the next round if the server is currently running. - -There are two flavors to this. -* The manual method is to use the repo page's merge PR button for fine grain control over PR merging. Each time this button is pressed, the latest commit of the specified PR# will be merged into the repo -* The managed method is the Server page's `Test Merge Manager` which uses the GitHub API to get information about available PRs. - * Open PRs are listed by default - * Currently test merged PRs are checked off and listed at the top when it's opened -* If a PR contains a label that contains the text `test` (case-insensitive) it will be listed on top and marked as `TESTING REQUESTED` - * If a PR has been updated since it was test merged, two entries for it will appear. One listed as `OUTDATED` and specifying the commit it was merged at - * You can change the initial update action of the server with the radio buttons in the bottom left. `Update to Remote` fully resets and updates the server before merging PRs. `Update To Origin` does the same based off the local repository's `origin` remote. `No Update` will merge the PRs without any prior action - * Checking off PRs here and then hitting apply will run the selected update action, optionally generate and push a changelog (see below), merge the PRs, and then compile. `Update Server` will keep any active merged PRs until they are manually removed or merged on the `origin` remote. - * Given that the control panel uses GitHub's API to populate the `Test Merge Manager` you may be prompted for your credentials if you make too many requests. This will create a personal access token with public access on your account specifying its use to bypass the GitHub API rate limit. - -You can clear all active test merges using `Reset to Origin Branch` in the `Repository` tab and the using `Copy from Repo and Compile` in the server tab (explained below). You can also use the `Reset All Test Merges` button on the server tab for a concise solution. - -### The Compiler -* `Server` -> `Copy from Repo and Compile` - * Copies the local repository code, compiles it, and stages it to apply next round -* `Server` -> `Initialize Game Folders` - * Requires the server not be running, rebuilds the `Game` folder and then does `Copy from Repo and Compile` - * Required on first setup - -### Starting everything when the computer/server boots -Just tick the `Autostart` option in the `Server` tab or run `dd autostart on` on the command line. As it's a windows service, it will automatically run without having to log ing - -### Enabling the BYOND Webclient -Just tick the `Webclient` option in the `Server` tab or run `dd webclient on` on the command line. - -### Static Configuration -* The `Static Files` page of the control panel lets you modify all files in directories you specified in `TGS3.json` -* The files are modified here using the Windows credentials of the active user, feel free to manually set ACLs on them -* You may optionally rebuild the entire `Static` folder from the repository using the `Recreate Static Directory` button. This will copy the original files from the repository based on the current `TGS3.json`. This requires DreamDaemon not be running - -### Modifying Your Code - -Any `.dm` files included in the root level of the `Static` directory are automatically copied over and included before anything else in your `.dme` for compilation. Use this to configure compile options as you see fit. - -### Moving, Renaming, and Detaching instances - -* Instances can be renamed, but this requires a temporary offlining of them (this includes interface access, DreamDaemon, and chat bots) -* Detaching an instance simply removes it from the main server's configuration and control, leaving it free for the user to manipulate -* Importing an instance will work as long as - 1. No other instance currently in the server has the same name - 1. It is not the same path as another instance - Note that importing an instance as a different windows user (this is always different across machines) will result in a loss of chat configuration due to the encryption scheme - -### Viewing Server Logs -* Service logs are stored in the Windows event viewer under `Windows Logs` -> `Application`. You'll need to filter this list for `TG Station Server` -* Every event type is keyed with an ID. A complete listing of these IDs and their purpose can be found [here](https://github.com/tgstation/tgstation-server/blob/master/TGS.Server/EventID.cs). Event IDs from different instances are offset by the logging ID the instance was assigned when it was started. -* You can also import the custom view `View TGS3 Logs.xml` in this folder to have them automatically filtered -* Servers running in console mode only use std_out - -### Enabling upstream changelog generation -* The repository will automatically create an ssh version of the initial origin remote and can optionally push generated changelogs to your git through it -* The repository can only authenticate using ssh public key authentication -* To enable this feature, simply create `public_key.txt` and `private_key.txt` in a folder called RepoKey in the server directory -* The private key must be in `-----BEGIN RSA PRIVATE KEY-----` format and the public key must be in `ssh-rsa` format. You can generate a keypair like this using the converter in the dropdown menu of PuTTYGen. See github guidelines for setting this up here: https://help.github.com/articles/connecting-to-github-with-ssh/ -* The server will be able to read these files regardless of their permissions, so the responsibility is on you to set their ACL's so they can't be read by those that shouldn't - -### Synchronized test merge commits -* An instance can push branchless test merge commits to GitHub if the `RepoKey` folder is setup -* This pushes all test merge commits to the remote branch `___TGS3TempBranch` and then deletes it -* This provides public reference information about the test merge commit and time since it will appear in the PR in questing -* To enable this, check the `Sync Commits` button on the `Repository` page of the control panel or run `repo push-testmerges on --instance ""` from TGCommandLine - -## CONTRIBUTING - -* See [CONTRIBUTING.md](https://github.com/tgstation/tgstation-server/blob/master/.github/CONTRIBUTING.md) - -## LICENSING - -* The DM API for the project is licensed under the MIT license. -* The /tg/station 13 icon is licensed under [Creative Commons 3.0 BY-SA](http://creativecommons.org/licenses/by-sa/3.0/). -* The remainder of the project is licensed under [GNU AGPL v3](http://www.gnu.org/licenses/agpl-3.0.html) - -See the bottom of each file in the /DMAPI tree for the MIT license diff --git a/legacy/V3/TGS.CommandLine/AdminCommands.cs b/legacy/V3/TGS.CommandLine/AdminCommands.cs deleted file mode 100644 index 41addc7b7b..0000000000 --- a/legacy/V3/TGS.CommandLine/AdminCommands.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Collections.Generic; -using TGS.Interface; - -namespace TGS.CommandLine -{ - sealed class AdminCommand : RootCommand - { - public AdminCommand() - { - Keyword = "admin"; - Children = new Command[] { new AdminViewGroupCommand(), new AdminSetGroupCommand(), new AdminClearGroupCommand(), new AdminRecreateStaticCommand() }; - } - public override string GetHelpText() - { - return "Manage instance authentication"; - } - } - - class AdminRecreateStaticCommand : ConsoleCommand - { - public AdminRecreateStaticCommand() - { - Keyword = "recreate-static-directory"; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.Administration.RecreateStaticFolder(); - OutputProc(res ?? "Success"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - - public override string GetHelpText() - { - return "Backup the current static directory and repopulate it from the TGS3.json in the repository"; - } - } - - class AdminViewGroupCommand : ConsoleCommand - { - public AdminViewGroupCommand() - { - Keyword = "view-group"; - } - public override string GetHelpText() - { - return "Print the name of the windows group that is allowed to use the service"; - } - - protected override ExitCode Run(IList parameters) - { - var group = Instance.Administration.GetCurrentAuthorizedGroup(); - OutputProc(group ?? "ERROR"); - return group != null ? ExitCode.Normal : ExitCode.ServerError; - } - } - - class AdminSetGroupCommand : ConsoleCommand - { - public AdminSetGroupCommand() - { - Keyword = "set-group"; - RequiredParameters = 1; - } - - public override string GetHelpText() - { - return "Set the windows group allowed to use the service"; - } - - public override string GetArgumentString() - { - return ""; - } - protected override ExitCode Run(IList parameters) - { - var result = Instance.Administration.SetAuthorizedGroup(parameters[0]); - if(result != null) - { - OutputProc("Group set to: " + result); - return ExitCode.Normal; - } - else - { - OutputProc("Failed to find a group named: " + parameters[0]); - return ExitCode.ServerError; - } - } - } - - class AdminClearGroupCommand : ConsoleCommand - { - public AdminClearGroupCommand() - { - Keyword = "clear-group"; - } - - public override string GetHelpText() - { - return "Clears the groups allowed to use the service, leaving only windows administrators"; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.Administration.SetAuthorizedGroup(null); - if(res != "ADMIN") - { - OutputProc("Failed to clear the group??? We are currently set to: " + res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } -} diff --git a/legacy/V3/TGS.CommandLine/App.config b/legacy/V3/TGS.CommandLine/App.config deleted file mode 100644 index 88fa4027bd..0000000000 --- a/legacy/V3/TGS.CommandLine/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.CommandLine/BYONDCommands.cs b/legacy/V3/TGS.CommandLine/BYONDCommands.cs deleted file mode 100644 index 84e1209781..0000000000 --- a/legacy/V3/TGS.CommandLine/BYONDCommands.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using TGS.Interface; - -namespace TGS.CommandLine -{ - class BYONDCommand : RootCommand - { - public BYONDCommand() - { - Keyword = "byond"; - Children = new Command[] { new BYONDUpdateCommand(), new BYONDVersionCommand(), new BYONDStatusCommand() }; - } - public override string GetHelpText() - { - return "Manage BYOND installation"; - } - } - - class BYONDVersionCommand : ConsoleCommand - { - public BYONDVersionCommand() - { - Keyword = "version"; - } - protected override ExitCode Run(IList parameters) - { - var type = ByondVersion.Installed; - if (parameters.Count > 0) - if (parameters[0].ToLower() == "--staged") - type = ByondVersion.Staged; - else if (parameters[0].ToLower() == "--latest") - type = ByondVersion.Latest; - OutputProc(Instance.Byond.GetVersion(type) ?? "Unistalled"); - return ExitCode.Normal; - } - public override string GetArgumentString() - { - return "[--staged|--latest]"; - } - - public override string GetHelpText() - { - return "Print the currently installed BYOND version"; - } - } - - - class BYONDStatusCommand : ConsoleCommand - { - public BYONDStatusCommand() - { - Keyword = "status"; - } - protected override ExitCode Run(IList parameters) - { - switch (Instance.Byond.CurrentStatus()) - { - case ByondStatus.Downloading: - OutputProc("Downloading update..."); - break; - case ByondStatus.Idle: - OutputProc("Updater Idle"); - break; - case ByondStatus.Staged: - OutputProc("Update staged and awaiting server restart"); - break; - case ByondStatus.Staging: - OutputProc("Staging update..."); - break; - case ByondStatus.Starting: - OutputProc("Starting update..."); - break; - case ByondStatus.Updating: - OutputProc("Applying update..."); - break; - default: - OutputProc("Limmexing (This is an error)."); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - public override string GetHelpText() - { - return "Print the current status of the BYOND updater"; - } - } - - class BYONDUpdateCommand : ConsoleCommand - { - public BYONDUpdateCommand() - { - Keyword = "update"; - RequiredParameters = 2; - } - protected override ExitCode Run(IList parameters) - { - int Major = 0, Minor = 0; - try - { - Major = Convert.ToInt32(parameters[0]); - Minor = Convert.ToInt32(parameters[1]); - } - catch - { - OutputProc("Please enter version as ."); - return ExitCode.BadCommand; - } - - var BYOND = Instance.Byond; - if (!BYOND.UpdateToVersion(Major, Minor)) - - { - OutputProc("Failed to begin update!"); - return ExitCode.ServerError; - } - - var stat = BYOND.CurrentStatus(); - while (stat != ByondStatus.Idle && stat != ByondStatus.Staged) - { - Thread.Sleep(100); - stat = BYOND.CurrentStatus(); - } - var res = BYOND.GetError(); - OutputProc(res ?? (stat == ByondStatus.Staged ? "Update staged and will apply next DD reboot" : "Update finished")); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - public override string GetArgumentString() - { - return " "; - } - public override string GetHelpText() - { - return "Updates the BYOND installation to the specified version"; - } - } -} diff --git a/legacy/V3/TGS.CommandLine/ChatCommands.cs b/legacy/V3/TGS.CommandLine/ChatCommands.cs deleted file mode 100644 index 6e87e34944..0000000000 --- a/legacy/V3/TGS.CommandLine/ChatCommands.cs +++ /dev/null @@ -1,721 +0,0 @@ -using System; -using System.Collections.Generic; -using TGS.Interface; - -namespace TGS.CommandLine -{ - class IRCCommand : RootCommand - { - public IRCCommand() - { - Keyword = "irc"; - Children = new Command[] { new IRCNickCommand(), new IRCAuthCommand(), new IRCDisableAuthCommand(), new IRCServerCommand(), new ChatJoinCommand(ChatProvider.IRC), new ChatPartCommand(ChatProvider.IRC), new ChatListAdminsCommand(ChatProvider.IRC), new ChatReconnectCommand(ChatProvider.IRC), new ChatAddminCommand(ChatProvider.IRC), new ChatDeadminCommand(ChatProvider.IRC), new ChatEnableCommand(ChatProvider.IRC), new ChatDisableCommand(ChatProvider.IRC), new ChatStatusCommand(ChatProvider.IRC), new IRCAuthModeCommand(), new IRCAuthLevelCommand() }; - } - public override string GetHelpText() - { - return "Manages the IRC bot"; - } - } - class DiscordCommand : RootCommand - { - public DiscordCommand() - { - Keyword = "discord"; - Children = new Command[] { new DiscordSetTokenCommand(), new ChatJoinCommand(ChatProvider.Discord), new ChatPartCommand(ChatProvider.Discord), new ChatListAdminsCommand(ChatProvider.Discord), new ChatReconnectCommand(ChatProvider.Discord), new ChatAddminCommand(ChatProvider.Discord), new ChatDeadminCommand(ChatProvider.Discord), new ChatEnableCommand(ChatProvider.Discord), new ChatDisableCommand(ChatProvider.Discord), new ChatStatusCommand(ChatProvider.Discord) , new DiscordAuthModeCommand() }; - } - public override string GetHelpText() - { - return "Manages the Discord bot"; - } - } - class IRCNickCommand : ConsoleCommand - { - public IRCNickCommand() - { - Keyword = "nick"; - RequiredParameters = 1; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Sets the IRC nickname"; - } - - protected override ExitCode Run(IList parameters) - { - var Chat = Instance.Chat; - Chat.SetProviderInfo(new IRCSetupInfo(Chat.ProviderInfos()[(int)ChatProvider.IRC]) - { - Nickname = parameters[0], - }); - return ExitCode.Normal; - } - } - - class ChatJoinCommand : ConsoleCommand - { - readonly int providerIndex; - public ChatJoinCommand(ChatProvider pI) - { - Keyword = "join"; - RequiredParameters = 2; - providerIndex = (int)pI; - } - - public override string GetArgumentString() - { - return " "; - } - public override string GetHelpText() - { - return "Joins a channel for listening and broadcasting of the specified message type (Developer, Watchdog, Game, Admin)"; - } - - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - var info = IRC.ProviderInfos()[providerIndex]; - List channels; - - switch (parameters[1].ToLower()) - { - case "dev": - channels = info.DevChannels; - break; - case "wd": - channels = info.WatchdogChannels; - break; - case "game": - channels = info.GameChannels; - break; - case "admin": - channels = info.AdminChannels; - break; - default: - OutputProc("Invalid parameter: " + parameters[1]); - return ExitCode.BadCommand; - } - - var lowerParam = parameters[0].ToLower(); - foreach (var I in channels) - { - if (I.ToLower() == lowerParam) - { - OutputProc("Already in this channel!"); - return ExitCode.BadCommand; - } - } - channels.Add(parameters[0]); - switch (parameters[1].ToLower()) - { - case "dev": - info.DevChannels = channels; - break; - case "wd": - info.WatchdogChannels = channels; - break; - case "game": - info.GameChannels = channels; - break; - case "admin": - info.AdminChannels = channels; - break; - } - var res = IRC.SetProviderInfo(info); - if(res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - class ChatPartCommand : ConsoleCommand - { - readonly int providerIndex; - public ChatPartCommand(ChatProvider pI) - { - Keyword = "part"; - RequiredParameters = 2; - providerIndex = (int)pI; - } - - public override string GetArgumentString() - { - return " "; - } - public override string GetHelpText() - { - return "Stops listening and broadcasting on a channel for the specified message type (Developer, Watchdog, Game, Admin)"; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - var info = IRC.ProviderInfos()[providerIndex]; - List channels; - - switch (parameters[1].ToLower()) - { - case "dev": - channels = info.DevChannels; - break; - case "wd": - channels = info.WatchdogChannels; - break; - case "game": - channels = info.GameChannels; - break; - case "admin": - channels = info.AdminChannels; - break; - default: - OutputProc("Invalid parameter: " + parameters[1]); - return ExitCode.BadCommand; - } - var lowerParam = parameters[0].ToLower(); - if ((ChatProvider)providerIndex == ChatProvider.IRC && lowerParam[0] != '#') - lowerParam = "#" + lowerParam; - channels.Remove(lowerParam); - switch (parameters[1].ToLower()) - { - case "dev": - info.DevChannels = channels; - break; - case "wd": - info.WatchdogChannels = channels; - break; - case "game": - info.GameChannels = channels; - break; - case "admin": - info.AdminChannels = channels; - break; - } - var res = IRC.SetProviderInfo(info); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - class ChatListAdminsCommand : ConsoleCommand - { - readonly int providerIndex; - public ChatListAdminsCommand(ChatProvider pI) - { - Keyword = "list-admins"; - providerIndex = (int)pI; - } - - public override string GetHelpText() - { - return "List users which can use restricted commands in the admin channel"; - } - - protected override ExitCode Run(IList parameters) - { - var info = Instance.Chat.ProviderInfos()[providerIndex]; - string authType; - switch ((ChatProvider)providerIndex) - { - case ChatProvider.IRC: - if (info.AdminsAreSpecial) - authType = "Mode:"; - else - authType = "Nicknames:"; - break; - case ChatProvider.Discord: - if (info.AdminsAreSpecial) - authType = "Role IDs:"; - else - authType = "User IDs:"; - break; - default: - OutputProc(String.Format("Invalid provider: {0}!", providerIndex)); - return ExitCode.ServerError; - } - OutputProc("Authorized " + authType); - if (info.AdminsAreSpecial && (ChatProvider)providerIndex == ChatProvider.IRC) - switch(new IRCSetupInfo(info).AuthLevel) - { - case IRCMode.Voice: - OutputProc("+"); - break; - case IRCMode.Halfop: - OutputProc("%"); - break; - case IRCMode.Op: - OutputProc("@"); - break; - case IRCMode.Owner: - OutputProc("~"); - break; - } - else - foreach (var I in info.AdminList) - OutputProc(I); - return ExitCode.Normal; - } - } - class ChatReconnectCommand : ConsoleCommand - { - readonly ChatProvider providerIndex; - public ChatReconnectCommand(ChatProvider pI) - { - Keyword = "reconnect"; - providerIndex = pI; - } - - public override string GetHelpText() - { - return "Restablish the chat connection"; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.Chat.Reconnect(providerIndex); - if (res != null) - { - OutputProc("Error: " + res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - class ChatAddminCommand : ConsoleCommand - { - readonly int providerIndex; - public ChatAddminCommand(ChatProvider pI) - { - Keyword = "addmin"; - RequiredParameters = 1; - providerIndex = (int)pI; - } - - public override string GetArgumentString() - { - return "[nick]"; - } - public override string GetHelpText() - { - return "Add a user which can use restricted commands in the admin channels"; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - var info = IRC.ProviderInfos()[providerIndex]; - var newmin = parameters[0].ToLower(); - - if (info.AdminsAreSpecial && (ChatProvider)providerIndex == ChatProvider.IRC) - { - OutputProc("Invalid auth mode for this command!"); - return ExitCode.BadCommand; - } - - if (info.AdminList.Contains(newmin)) - { - OutputProc(parameters[0] + " is already an admin!"); - return ExitCode.BadCommand; - } - var al = info.AdminList; - al.Add(newmin); - info.AdminList = al; - var res = IRC.SetProviderInfo(info); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - class IRCAuthModeCommand : ConsoleCommand - { - public IRCAuthModeCommand() - { - Keyword = "set-auth-mode"; - RequiredParameters = 1; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Switch between admin command authorization via user channel mode or nicknames"; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - var info = IRC.ProviderInfos()[(int)ChatProvider.IRC]; - var lowerparam = parameters[0].ToLower(); - if (lowerparam == "channel-mode") - info.AdminsAreSpecial = true; - else if (lowerparam == "nickname") - info.AdminsAreSpecial = false; - else - { - OutputProc("Invalid parameter: " + parameters[0]); - return ExitCode.BadCommand; - } - var res = IRC.SetProviderInfo(info); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - class DiscordAuthModeCommand : ConsoleCommand - { - public DiscordAuthModeCommand() - { - Keyword = "set-auth-mode"; - RequiredParameters = 1; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Switch between admin command authorization via user roles or individual users"; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - var info = IRC.ProviderInfos()[(int)ChatProvider.Discord]; - var lowerparam = parameters[0].ToLower(); - if (lowerparam == "role-id") - info.AdminsAreSpecial = true; - else if (lowerparam == "user-id") - info.AdminsAreSpecial = false; - else - { - OutputProc("Invalid parameter: " + parameters[0]); - return ExitCode.BadCommand; - } - var res = IRC.SetProviderInfo(info); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - class IRCAuthLevelCommand : ConsoleCommand - { - public IRCAuthLevelCommand() - { - Keyword = "set-auth-level"; - RequiredParameters = 1; - } - - public override string GetArgumentString() - { - return "<+|%|@|~>"; - } - public override string GetHelpText() - { - return "Set the required channel mode for users to use admin commands"; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - var info = new IRCSetupInfo(IRC.ProviderInfos()[(int)ChatProvider.IRC]); - switch (parameters[0]) - { - case "+": - info.AuthLevel = IRCMode.Voice; - break; - case "%": - info.AuthLevel = IRCMode.Halfop; - break; - case "@": - info.AuthLevel = IRCMode.Op; - break; - case "~": - info.AuthLevel = IRCMode.Owner; - break; - default: - OutputProc("Invalid parameter: " + parameters[0]); - return ExitCode.BadCommand; - } - - var res = IRC.SetProviderInfo(info); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - class ChatDeadminCommand : ConsoleCommand - { - readonly int providerIndex; - public ChatDeadminCommand(ChatProvider pI) - { - Keyword = "deadmin"; - RequiredParameters = 1; - providerIndex = (int)pI; - } - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Remove a user which can use restricted commands in the admin channels"; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - var info = IRC.ProviderInfos()[providerIndex]; - var newmin = parameters[0].ToLower(); - - if (info.AdminsAreSpecial && (ChatProvider)providerIndex == ChatProvider.IRC) - { - OutputProc("Invalid auth mode for this command!"); - return ExitCode.BadCommand; - } - - if (!info.AdminList.Contains(newmin)) - { - OutputProc(parameters[0] + " is not an admin!"); - return ExitCode.BadCommand; - } - - var al = info.AdminList; - al.Remove(newmin); - info.AdminList = al; - var res = IRC.SetProviderInfo(info); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - class IRCAuthCommand : ConsoleCommand - { - public IRCAuthCommand() - { - Keyword = "setup-auth"; - RequiredParameters = 2; - } - - public override string GetArgumentString() - { - return " "; - } - public override string GetHelpText() - { - return "Set the authentication message to send to target for identification. e.g. NickServ \"identify hunter2\""; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - IRC.SetProviderInfo(new IRCSetupInfo(IRC.ProviderInfos()[(int)ChatProvider.IRC]) - { - AuthTarget = parameters[0], - AuthMessage = parameters[1] - }); - return ExitCode.Normal; - } - } - - class IRCDisableAuthCommand : ConsoleCommand - { - public IRCDisableAuthCommand() - { - Keyword = "disable-auth"; - } - public override string GetHelpText() - { - return "Turns off IRC authentication"; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - IRC.SetProviderInfo(new IRCSetupInfo(IRC.ProviderInfos()[(int)ChatProvider.IRC]) - { - AuthTarget = null, - AuthMessage = null, - }); - return ExitCode.Normal; - } - } - - class ChatStatusCommand : ConsoleCommand - { - readonly int providerIndex; - public ChatStatusCommand(ChatProvider pI) - { - Keyword = "status"; - providerIndex = (int)pI; - } - public override string GetHelpText() - { - return "Lists channels and connections status"; - } - protected override ExitCode Run(IList parameters) - { - var IRC = Instance.Chat; - var info = IRC.ProviderInfos()[providerIndex]; - OutputProc("Currently configured channels:"); - OutputProc("Admin:"); - foreach (var I in info.AdminChannels) - OutputProc("\t" + I); - OutputProc("Watchdog:"); - foreach (var I in info.WatchdogChannels) - OutputProc("\t" + I); - OutputProc("Game:"); - foreach (var I in info.GameChannels) - OutputProc("\t" + I); - OutputProc("Developer:"); - foreach (var I in info.DevChannels) - OutputProc("\t" + I); - OutputProc("Chat bot is: " + (!info.Enabled ? "Disabled" : IRC.Connected(info.Provider) ? "Connected" : "Disconnected")); - return ExitCode.Normal; - } - } - class ChatEnableCommand : ConsoleCommand - { - readonly int providerIndex; - public ChatEnableCommand(ChatProvider pI) - { - Keyword = "enable"; - providerIndex = (int)pI; - } - - public override string GetHelpText() - { - return "Enables the chat bot"; - } - - protected override ExitCode Run(IList parameters) - { - var Chat = Instance.Chat; - var info = Chat.ProviderInfos()[providerIndex]; - info.Enabled = true; - var res = Chat.SetProviderInfo(info); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - class ChatDisableCommand : ConsoleCommand - { - readonly int providerIndex; - public ChatDisableCommand(ChatProvider pI) - { - Keyword = "disable"; - providerIndex = (int)pI; - } - - public override string GetHelpText() - { - return "Disables the chat bot"; - } - - protected override ExitCode Run(IList parameters) - { - var Chat = Instance.Chat; - var info = Chat.ProviderInfos()[providerIndex]; - info.Enabled = false; - var res = Chat.SetProviderInfo(info); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - class IRCServerCommand : ConsoleCommand - { - public IRCServerCommand() - { - Keyword = "set-server"; - RequiredParameters = 1; - } - public override string GetArgumentString() - { - return ":"; - } - public override string GetHelpText() - { - return "Sets the IRC server"; - } - - protected override ExitCode Run(IList parameters) - { - var splits = parameters[0].Split(':'); - if(splits.Length < 2) - { - OutputProc("Invalid parameter!"); - return ExitCode.BadCommand; - } - var Chat = Instance.Chat; - var PI = new IRCSetupInfo(Chat.ProviderInfos()[(int)ChatProvider.IRC]) - { - URL = splits[0] - }; - try - { - PI.Port = Convert.ToUInt16(splits[1]); - } - catch - { - OutputProc("Invalid port number!"); - return ExitCode.BadCommand; - } - - var res = Chat.SetProviderInfo(PI); - OutputProc(res ?? "Success"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } - - class DiscordSetTokenCommand : ConsoleCommand - { - public DiscordSetTokenCommand() - { - Keyword = "set-token"; - RequiredParameters = 1; - } - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Sets the discord API bot token"; - } - protected override ExitCode Run(IList parameters) - { - var Chat = Instance.Chat; - var res = Chat.SetProviderInfo(new DiscordSetupInfo(Chat.ProviderInfos()[(int)ChatProvider.Discord]) { BotToken = parameters[0] }); - OutputProc(res ?? "Success"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } -} \ No newline at end of file diff --git a/legacy/V3/TGS.CommandLine/ConfigCommands.cs b/legacy/V3/TGS.CommandLine/ConfigCommands.cs deleted file mode 100644 index 77c47bfaf9..0000000000 --- a/legacy/V3/TGS.CommandLine/ConfigCommands.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using TGS.Interface; - -namespace TGS.CommandLine -{ - class ConfigCommand : RootCommand - { - public ConfigCommand() - { - Keyword = "config"; - Children = new Command[] { new ConfigDeleteCommand(), new ConfigServerDirectoryCommand(), new ConfigDownloadCommand(), new ConfigUploadCommand(), new ConfigListCommand() }; - } - public override string GetHelpText() - { - return "Manage settings"; - } - } - class ConfigDeleteCommand : ConsoleCommand - { - public ConfigDeleteCommand() - { - Keyword = "delete"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.Config.DeleteFile(parameters[0], out bool unauthorized); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Deletes the specified file from the static tree."; - } - } - - class ConfigListCommand : ConsoleCommand - { - public ConfigListCommand() - { - Keyword = "list"; - } - - public override string GetHelpText() - { - return "Lists the contents of the server's static directory, optionally specifying a subdirectory"; - } - - public override string GetArgumentString() - { - return "[path to subdirectory]"; - } - protected override ExitCode Run(IList parameters) - { - var list = Instance.Config.ListStaticDirectory(parameters.Count > 0 ? parameters[0] : null, out string error, out bool unauthorized); - if(list == null) - { - OutputProc(error); - return ExitCode.ServerError; - } - if (list.Count == 0) - OutputProc("The static directory is empty!"); - else - foreach (var I in list) - OutputProc(I); - return ExitCode.Normal; - } - } - - class ConfigServerDirectoryCommand : ConsoleCommand - { - public ConfigServerDirectoryCommand() - { - Keyword = "server-dir"; - } - - protected override ExitCode Run(IList parameters) - { - OutputProc(Instance.ServerDirectory()); - return ExitCode.Normal; - } - - public override string GetHelpText() - { - return "Print the directory the server is installed in"; - } - } - - class ConfigDownloadCommand : ConsoleCommand - { - public ConfigDownloadCommand() - { - Keyword = "download"; - RequiredParameters = 2; - } - - protected override ExitCode Run(IList parameters) - { - var bytes = Instance.Config.ReadText(parameters[0], parameters.Count > 2 && parameters[2].ToLower() == "--repo", out string error, out bool unauthorized); - if(bytes == null) - { - OutputProc("Error: " + error); - return ExitCode.ServerError; - } - - try - { - File.WriteAllText(parameters[1], bytes); - } - catch (Exception e) - { - OutputProc("Error: " + e.ToString()); - } - - return ExitCode.Normal; - } - public override string GetArgumentString() - { - return " [--repo]"; - } - public override string GetHelpText() - { - return "Downloads the specified file from the static tree and writes it to out file. --repo will fetch it from the repository instead of the Static directory"; - } - } - - class ConfigUploadCommand : ConsoleCommand - { - public ConfigUploadCommand() - { - Keyword = "upload"; - RequiredParameters = 2; - } - - protected override ExitCode Run(IList parameters) - { - try - { - var res = Instance.Config.WriteText(parameters[0], File.ReadAllText(parameters[1]), null, out bool unauthorized); - if (res != null) - { - OutputProc("Error: " + res); - return ExitCode.ServerError; - } - } - catch (Exception e) - { - OutputProc("Error: " + e.ToString()); - } - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return " [--repo]"; - } - - public override string GetHelpText() - { - return "Uploads the specified file to the static tree from source file"; - } - } -} diff --git a/legacy/V3/TGS.CommandLine/ConsoleCommand.cs b/legacy/V3/TGS.CommandLine/ConsoleCommand.cs deleted file mode 100644 index 56b0381ee1..0000000000 --- a/legacy/V3/TGS.CommandLine/ConsoleCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using TGS.Interface; - -namespace TGS.CommandLine -{ - abstract class ConsoleCommand : Command - { - /// - /// The currently in use by the - /// - public static IServer Server; - /// - /// The currently in use by the - /// - public static IInstance Instance; - } -} diff --git a/legacy/V3/TGS.CommandLine/DDCommands.cs b/legacy/V3/TGS.CommandLine/DDCommands.cs deleted file mode 100644 index 41ad8fe94d..0000000000 --- a/legacy/V3/TGS.CommandLine/DDCommands.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.Collections.Generic; -using TGS.Interface; - -namespace TGS.CommandLine -{ - class DDCommand : RootCommand - { - public DDCommand() - { - Keyword = "dd"; - Children = new Command[] { new DDStartCommand(), new DDStopCommand(), new DDRestartCommand(), new DDStatusCommand(), new DDAutostartCommand(), new DDPortCommand(), new DDSecurityCommand(), new DDWorldAnnounceCommand(), new DDWebclientCommand() }; - } - public override string GetHelpText() - { - return "Manage DreamDaemon"; - } - } - - class DDWorldAnnounceCommand : ConsoleCommand - { - public DDWorldAnnounceCommand() - { - Keyword = "announce"; - RequiredParameters = 1; - } - - public override string GetHelpText() - { - return "Sends a message all players on the server"; - } - - public override string GetArgumentString() - { - return ""; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.DreamDaemon.WorldAnnounce(String.Join(" ", parameters)); - OutputProc(res ?? "Success!"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } - - class DDStartCommand : ConsoleCommand - { - public DDStartCommand() - { - Keyword = "start"; - } - - public override string GetHelpText() - { - return "Starts the server and watchdog"; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.DreamDaemon.Start(); - OutputProc(res ?? "Success!"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } - - class DDStopCommand : ConsoleCommand - { - public DDStopCommand() - { - Keyword = "stop"; - } - public override string GetArgumentString() - { - return "[--graceful]"; - } - - public override string GetHelpText() - { - return "Stops the server and watchdog optionally waiting for the current round to end"; - } - - protected override ExitCode Run(IList parameters) - { - var DD = Instance.DreamDaemon; - if (parameters.Count > 0 && parameters[0].ToLower() == "--graceful") - { - if (DD.DaemonStatus() != DreamDaemonStatus.Online) - { - OutputProc("Error: The game is not currently running!"); - return ExitCode.ServerError; - } - DD.RequestStop(); - return ExitCode.Normal; - } - var res = DD.Stop(); - OutputProc(res ?? "Success!"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } - class DDRestartCommand : ConsoleCommand - { - public DDRestartCommand() - { - Keyword = "restart"; - } - - public override string GetArgumentString() - { - return "[--graceful]"; - } - protected override ExitCode Run(IList parameters) - { - var DD = Instance.DreamDaemon; - if (parameters.Count > 0 && parameters[0].ToLower() == "--graceful") - { - if (DD.DaemonStatus() != DreamDaemonStatus.Online) - { - OutputProc("Error: The game is not currently running!"); - return ExitCode.ServerError; - } - DD.RequestRestart(); - return ExitCode.Normal; - } - var res = DD.Restart(); - OutputProc(res ?? "Success!"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - - public override string GetHelpText() - { - return "Restarts the server and watchdog optionally waiting for the current round to end"; - } - } - class DDStatusCommand : ConsoleCommand - { - public DDStatusCommand() - { - Keyword = "status"; - } - - public override string GetHelpText() - { - return "Gets the current status of the watchdog and server"; - } - - protected override ExitCode Run(IList parameters) - { - var DD = Instance.DreamDaemon; - OutputProc(DD.StatusString(true)); - if (DD.ShutdownInProgress()) - OutputProc("The server will shutdown once the current round completes."); - var pc = DD.PlayerCount(); - if (pc != -1) - OutputProc(pc + " connected clients"); - return ExitCode.Normal; - } - } - - class DDAutostartCommand : ConsoleCommand - { - public DDAutostartCommand() - { - Keyword = "autostart"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - var DD = Instance.DreamDaemon; - switch (parameters[0].ToLower()) - { - case "on": - DD.SetAutostart(true); - break; - case "off": - DD.SetAutostart(false); - break; - case "check": - OutputProc("Autostart is: " + (DD.Autostart() ? "On" : "Off")); - break; - default: - OutputProc("Invalid parameter: " + parameters[0]); - return ExitCode.BadCommand; - } - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Change or check autostarting of the game server with the service"; - } - } - class DDWebclientCommand : ConsoleCommand - { - public DDWebclientCommand() - { - Keyword = "webclient"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - var DD = Instance.DreamDaemon; - switch (parameters[0].ToLower()) - { - case "on": - DD.SetWebclient(true); - break; - case "off": - DD.SetWebclient(false); - break; - case "check": - OutputProc("Webclient is: " + (DD.Webclient() ? "Enabled" : "Disabled")); - break; - default: - OutputProc("Invalid parameter: " + parameters[0]); - return ExitCode.BadCommand; - } - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Change or check if the BYOND webclient is enabled for the game server"; - } - } - - class DDPortCommand : ConsoleCommand - { - public DDPortCommand() - { - Keyword = "set-port"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - ushort port; - try - { - port = Convert.ToUInt16(parameters[0]); - } - catch - { - OutputProc("Invalid port number!"); - return ExitCode.BadCommand; - } - - Instance.DreamDaemon.SetPort(port); - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - - public override string GetHelpText() - { - return "Sets the port DreamDaemon will open the server on. Requires a server restart to apply and queues a graceful one up"; - } - } - - class DDSecurityCommand : ConsoleCommand - { - public DDSecurityCommand() - { - Keyword = "set-security"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - DreamDaemonSecurity sec; - switch (parameters[0].ToLower()) - { - case "safe": - sec = DreamDaemonSecurity.Safe; - break; - case "ultra": - case "ultrasafe": - sec = DreamDaemonSecurity.Ultrasafe; - break; - case "trust": - case "trusted": - sec = DreamDaemonSecurity.Trusted; - break; - default: - OutputProc("Invalid security word!"); - return ExitCode.BadCommand; - } - Instance.DreamDaemon.SetSecurityLevel(sec); - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - - public override string GetHelpText() - { - return "Sets the visibility option for the DreamDaemon world"; - } - } -} diff --git a/legacy/V3/TGS.CommandLine/DMCommands.cs b/legacy/V3/TGS.CommandLine/DMCommands.cs deleted file mode 100644 index 47d9959e4f..0000000000 --- a/legacy/V3/TGS.CommandLine/DMCommands.cs +++ /dev/null @@ -1,218 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using TGS.Interface; - -namespace TGS.CommandLine -{ - class DMCommand : RootCommand - { - public DMCommand() - { - Keyword = "dm"; - Children = new Command[] { new DMCompileCommand(), new DMInitializeCommand(), new DMStatusCommand(), new DMSetProjectNameCommand(), new DMCancelCommand() }; - } - public override string GetHelpText() - { - return "Manage compiling the server"; - } - } - - class DMCompileCommand : ConsoleCommand - { - public DMCompileCommand() - { - Keyword = "compile"; - } - - protected override ExitCode Run(IList parameters) - { - var DM = Instance.Compiler; - var stat = DM.GetStatus(); - if (stat != CompilerStatus.Initialized) - { - OutputProc("Error: Compiler is " + ((stat == CompilerStatus.Uninitialized) ? "unintialized!" : "busy with another task!")); - return ExitCode.ServerError; - } - - if (Instance.Byond.GetVersion(ByondVersion.Installed) == null) - { - Console.Write("Error: BYOND is not installed!"); - return ExitCode.ServerError; - } - - if (!DM.Compile()) - { - OutputProc("Error: Unable to start compilation!"); - var err = DM.CompileError(); - if (err != null) - OutputProc(err); - return ExitCode.ServerError; - } - OutputProc("Compile job started"); - if (parameters.Count > 0 && parameters[0] == "--wait") - { - do - { - Thread.Sleep(1000); - } while (DM.GetStatus() == CompilerStatus.Compiling); - var res = DM.CompileError(); - OutputProc(res ?? "Compilation successful"); - if (res != null) - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return "[--wait]"; - } - - public override string GetHelpText() - { - return "Starts a compile/update job optionally waiting for completion"; - } - } - class DMStatusCommand : ConsoleCommand - { - public DMStatusCommand() - { - Keyword = "status"; - } - - void ShowError() - { - var error = Instance.Compiler.CompileError(); - if (error != null) - OutputProc("Last error: " + error); - } - - protected override ExitCode Run(IList parameters) - { - var DM = Instance.Compiler; - OutputProc(String.Format("Target Project: /{0}.dme", DM.ProjectName())); - Console.Write("Compilier is currently: "); - switch (DM.GetStatus()) - { - case CompilerStatus.Compiling: - OutputProc("Compiling..."); - break; - case CompilerStatus.Initialized: - OutputProc("Idle"); - ShowError(); - break; - case CompilerStatus.Initializing: - OutputProc("Setting up..."); - break; - case CompilerStatus.Uninitialized: - OutputProc("Uninitialized"); - ShowError(); - break; - default: - OutputProc("Seizing the means of production (This is an error)."); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - - public override string GetHelpText() - { - return "Get the current status of the compiler"; - } - } - - class DMSetProjectNameCommand : ConsoleCommand - { - public DMSetProjectNameCommand() - { - Keyword = "project-name"; - RequiredParameters = 1; - } - public override string GetArgumentString() - { - return ""; - } - - public override string GetHelpText() - { - return "Set the relative path of the .dme/.dmb to compile/run"; - } - - protected override ExitCode Run(IList parameters) - { - Instance.Compiler.SetProjectName(parameters[0]); - return ExitCode.Normal; - } - } - - class DMInitializeCommand : ConsoleCommand - { - public DMInitializeCommand() - { - Keyword = "initialize"; - } - - public override string GetArgumentString() - { - return "[--wait]"; - } - - public override string GetHelpText() - { - return "Starts an initialization job optionally waiting for completion"; - } - - protected override ExitCode Run(IList parameters) - { - var DM = Instance.Compiler; - var stat = DM.GetStatus(); - if (stat == CompilerStatus.Compiling || stat == CompilerStatus.Initializing) - { - OutputProc("Error: Compiler is " + ((stat == CompilerStatus.Initializing) ? "already initialized!" : " already running!")); - return ExitCode.ServerError; - } - if (!DM.Initialize()) - { - OutputProc("Error: Unable to start initialization!"); - var err = DM.CompileError(); - if (err != null) - OutputProc(err); - return ExitCode.ServerError; - } - OutputProc("Initialize job started"); - if (parameters.Count > 0 && parameters[0] == "--wait") - { - do - { - Thread.Sleep(1000); - } while (DM.GetStatus() == CompilerStatus.Initializing); - var res = DM.CompileError(); - OutputProc(res ?? "Initialization successful"); - if (res != null) - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - class DMCancelCommand : ConsoleCommand - { - public DMCancelCommand() - { - Keyword = "cancel"; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.Compiler.Cancel(); - OutputProc(res ?? "Success!"); - return ExitCode.Normal; //because failing cancellation implys it's already cancelled - } - - public override string GetHelpText() - { - return "Cancels the current compilation job"; - } - } -} diff --git a/legacy/V3/TGS.CommandLine/Program.cs b/legacy/V3/TGS.CommandLine/Program.cs deleted file mode 100644 index 369652de33..0000000000 --- a/legacy/V3/TGS.CommandLine/Program.cs +++ /dev/null @@ -1,281 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.CommandLine -{ - class Program - { - static bool interactive = false, saidSrvVersion = false; - static IClient currentInterface; - static Command.ExitCode RunCommandLine(IList argsAsList) - { - //first lookup the connection string - bool badConnectionString = false; - for (var I = 0; I < argsAsList.Count - 1; ++I) { - var lowerarg = argsAsList[I].ToLower(); - if (lowerarg == "-c" || lowerarg == "--connect") - { - var connectionString = argsAsList[I + 1]; - var splits = connectionString.Split('@'); - var userpass = splits[0].Split(':'); - if (splits.Length != 2 || userpass.Length != 2) - { - badConnectionString = true; - break; - } - var addrport = splits[1].Split(':'); - if (addrport.Length != 2) - { - badConnectionString = true; - break; - } - var username = userpass[0]; - var password = userpass[1]; - var address = addrport[0]; - ushort port; - try - { - port = Convert.ToUInt16(addrport[1]); - } - catch - { - badConnectionString = true; - break; - } - if(String.IsNullOrWhiteSpace(username) || String.IsNullOrWhiteSpace(password) || String.IsNullOrWhiteSpace(address)) - { - badConnectionString = true; - break; - } - argsAsList.RemoveAt(I); - argsAsList.RemoveAt(I); - ReplaceInterface(new Client(new RemoteLoginInfo(address, port, username, password))); - break; - } - } - - if (badConnectionString) - { - Console.WriteLine("Remote connection usage: <-c/--connect> username:password@address:port"); - return Command.ExitCode.BadCommand; - } - - var res = currentInterface.ConnectionStatus(out string error); - if (!res.HasFlag(ConnectivityLevel.Connected)) - { - Console.WriteLine("Unable to connect to service: " + error); - Console.WriteLine("Remote connection usage: <-c/--connect> username:password@address:port"); - return Command.ExitCode.ConnectionError; - } - - if (!res.HasFlag(ConnectivityLevel.Authenticated)) - { - Console.WriteLine("Authentication error: Username/password/windows identity is not authorized!"); - return Command.ExitCode.ConnectionError; - } - - if (!SentVMMWarning && currentInterface.VersionMismatch(out error)) - { - SentVMMWarning = true; - Console.WriteLine(error); - } - else if (interactive && !saidSrvVersion) - { - Console.WriteLine("Connectd to service version: " + currentInterface.Server.Version); - saidSrvVersion = true; - } - - try - { - return new CLICommand(currentInterface).DoRun(argsAsList); - } - catch (Exception e) - { - Console.WriteLine("Error: " + e.ToString()); - return Command.ExitCode.ConnectionError; - }; - } - - static void ReplaceInterface(IClient I) - { - currentInterface = I; - ConsoleCommand.Server = I.Server; - ConsoleCommand.Instance = null; - saidSrvVersion = false; - } - - public static string ReadLineSecure() - { - string result = ""; - while (true) - { - ConsoleKeyInfo i = Console.ReadKey(true); - if (i.Key == ConsoleKey.Enter) - { - break; - } - else if (i.Key == ConsoleKey.Backspace) - { - if (result.Length > 0) - { - result = result.Substring(0, result.Length - 1); - Console.Write("\b \b"); - } - } - else - { - result += i.KeyChar; - Console.Write("*"); - } - } - Console.WriteLine(); - return result; - } - static bool SentVMMWarning = false; - static string AcceptedBadCert; - static bool BadCertificateInteractive(string message) - { - if (AcceptedBadCert == message) - return true; - Console.WriteLine(message); - Console.Write("Do you wish to continue? NOT RECCOMENDED! (y/N): "); - var result = Console.ReadLine().Trim().ToLower(); - if (result == "y" || result == "yes") - { - AcceptedBadCert = message; - return true; - } - return false; - } - - /// - /// Tries to set 's to , outputting appropriate messages - /// - /// The name of the to test - /// If , does not output on success - /// if the connection was made, otherwise - static bool CheckInstanceConnectivity(string instanceName, bool silentSuccess) - { - var res = currentInterface.Server.Instances.Where(x => x.Metadata.Name == instanceName).FirstOrDefault(); - if (res != null) - { - ConsoleCommand.Instance = res; - if (!silentSuccess) - Console.WriteLine("Successfully conected to instance!"); - return true; - } - Console.WriteLine("Unable to connect to instance! Does it exist?"); - return false; - } - - static int Main(string[] args) - { - ReplaceInterface(new Client()); - Command.OutputProcVar.Value = Console.WriteLine; - if (args.Length != 0) - { - var argsAsList = new List(args); - for (var I = 0; I < argsAsList.Count - 1; ++I) - { - if (argsAsList[I].ToLower() == "--instance") - { - if (!CheckInstanceConnectivity(args[I + 1], true)) - return (int)Command.ExitCode.ConnectionError; - argsAsList.RemoveRange(I, 2); - break; - } - else if (argsAsList[I].ToLower() == "--disable-ssl-verification") //im just not even going to document this because i hate it so much - { - argsAsList.RemoveAt(I); - --I; - Client.SetBadCertificateHandler(_ => false); - } - } - return (int)RunCommandLine(argsAsList); - } - //interactive mode - Client.SetBadCertificateHandler(BadCertificateInteractive); - Console.WriteLine("Type 'instance' to connect to a server instance"); - Console.WriteLine("Type 'remote' to connect to a remote service"); - while (true) - { - Console.Write("Enter command: "); - var NextCommand = Console.ReadLine(); - switch (NextCommand.ToLower()) - { - case "instance": - Console.Write("Enter instance name: "); - CheckInstanceConnectivity(Console.ReadLine(), false); - break; - case "remote": - SentVMMWarning = false; - Console.Write("Enter server address: "); - var address = Console.ReadLine(); - Console.Write("Enter server port: "); - ushort port; - try{ - port = Convert.ToUInt16(Console.ReadLine()); - } - catch - { - Console.WriteLine("Error: Bad port!"); - break; - } - Console.Write("Enter username: "); - var username = Console.ReadLine(); - Console.Write("Enter password: "); - var password = ReadLineSecure(); - ReplaceInterface(new Client(new RemoteLoginInfo(address, port, username, password))); - var res = currentInterface.ConnectionStatus(out string error); - if (!res.HasFlag(ConnectivityLevel.Connected)) - { - Console.WriteLine("Unable to connect: " + error); - ReplaceInterface(new Client()); - } - else if (!res.HasFlag(ConnectivityLevel.Authenticated)) - { - Console.WriteLine("Authentication error: Username/password/windows identity is not authorized! Returning to local mode..."); - ReplaceInterface(new Client()); - } - else - { - Console.WriteLine("Connected remotely"); - if (currentInterface.VersionMismatch(out error)) - { - SentVMMWarning = true; - Console.WriteLine(error); - } - Console.WriteLine("Type 'disconnect' to return to local mode"); - } - break; - case "disconnect": - SentVMMWarning = false; - ReplaceInterface(new Client()); - Console.WriteLine("Switch to local mode"); - break; - case "quit": - case "exit": - return (int)Command.ExitCode.Normal; - case "debug-upgrade": - currentInterface.Server.Management.PrepareForUpdate(); - return (int)Command.ExitCode.Normal; - default: - //linq voodoo to get quoted strings - var formattedCommand = NextCommand.Split('"') - .Select((element, index) => index % 2 == 0 // If even index - ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) // Split the item - : new string[] { element }) // Keep the entire item - .SelectMany(element => element).ToList(); - - formattedCommand = formattedCommand.Select(x => x.Trim()).ToList(); - formattedCommand.Remove(""); - RunCommandLine(formattedCommand); - break; - } - } - } - } -} diff --git a/legacy/V3/TGS.CommandLine/Properties/AssemblyInfo.cs b/legacy/V3/TGS.CommandLine/Properties/AssemblyInfo.cs deleted file mode 100644 index ba9129eaba..0000000000 --- a/legacy/V3/TGS.CommandLine/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TGStation Server Commandline")] -[assembly: AssemblyDescription("CLI for the TG Station Server Service")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9ad1f086-a83e-4d14-a844-58a9471106b6")] diff --git a/legacy/V3/TGS.CommandLine/RepoCommands.cs b/legacy/V3/TGS.CommandLine/RepoCommands.cs deleted file mode 100644 index 30a7a0613d..0000000000 --- a/legacy/V3/TGS.CommandLine/RepoCommands.cs +++ /dev/null @@ -1,407 +0,0 @@ -using System; -using System.Collections.Generic; -using TGS.Interface; - -namespace TGS.CommandLine -{ - class RepoCommand : RootCommand - { - public RepoCommand() - { - Keyword = "repo"; - Children = new Command[] { new RepoSetupCommand(), new RepoUpdateCommand(), new RepoGenChangelogCommand(), new RepoPushChangelogCommand(), new RepoSetEmailCommand(), new RepoSetNameCommand(), new RepoMergePRCommand(), new RepoListPRsCommand(), new RepoStatusCommand(), new RepoListBackupsCommand(), new RepoCheckoutCommand(), new RepoResetCommand(), new RepoUpdateJsonCommand(), new RepoSetPushTestmergeCommitsCommand() }; - } - public override string GetHelpText() - { - return "Manage the git repository"; - } - } - - class RepoSetPushTestmergeCommitsCommand : ConsoleCommand - { - public RepoSetPushTestmergeCommitsCommand() - { - Keyword = "push-testmerges"; - RequiredParameters = 1; - } - public override string GetHelpText() - { - return "Set if a temporary branch is to the remote when we make testmerge commits and then delete it"; - } - - public override string GetArgumentString() - { - return ""; - } - - protected override ExitCode Run(IList parameters) - { - switch (parameters[0].ToLower()) - { - case "on": - Instance.Repository.SetPushTestmergeCommits(true); - break; - case "off": - Instance.Repository.SetPushTestmergeCommits(false); - break; - default: - OutputProc("Invalid option!"); - return ExitCode.BadCommand; - } - return ExitCode.Normal; - } - } - - class RepoUpdateJsonCommand : ConsoleCommand - { - public RepoUpdateJsonCommand() - { - Keyword = "update-json"; - } - - public override string GetHelpText() - { - return "Updates the cached TGS3.json with the one from the repo. Compilation is blocked if these two do not match."; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.Repository.UpdateTGS3Json(); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - class RepoSetupCommand : ConsoleCommand - { - public RepoSetupCommand() - { - Keyword = "setup"; - RequiredParameters = 1; - } - protected override ExitCode Run(IList parameters) - { - var res = Instance.Repository.Setup(parameters[0], parameters.Count > 1 ? parameters[1] : "master"); - if (res != null) - { - OutputProc("Error: " + res); - return ExitCode.ServerError; - } - OutputProc("Setting up repo. This will take a while..."); - return ExitCode.Normal; - } - public override string GetArgumentString() - { - return " [branchname]"; - } - public override string GetHelpText() - { - return "Clean up everything and clones the repo at git-url with optional branch name"; - } - } - - class RepoStatusCommand : ConsoleCommand - { - public RepoStatusCommand() - { - Keyword = "status"; - } - protected override ExitCode Run(IList parameters) - { - var Repo = Instance.Repository; - var busy = Repo.OperationInProgress(); - if (!busy) - { - OutputProc("Repo: Idle"); - var head = Repo.GetHead(false, out string error); - if (head == null) - head = "Error: " + error; - var remotehead = Repo.GetHead(true, out error); - if (remotehead == null) - remotehead = "Error: " + error; - var branch = Repo.GetBranch(out error); - if (branch == null) - branch = "Error: " + error; - var remote = Repo.GetRemote(out error); - if (remote == null) - remote = "Error: " + error; - OutputProc("Remote: " + remote + " (" + remotehead + ")"); - OutputProc("Branch: " + branch); - OutputProc("HEAD: " + head); - OutputProc("Push testmerge commits: " + (Repo.PushTestmergeCommits() ? "ON" : "OFF")); - OutputProc(String.Format("Committer Identity: {0} ({1})", Repo.GetCommitterName(), Repo.GetCommitterEmail())); - } - else - { - OutputProc("Repo: Busy"); - var progress = Repo.CheckoutProgress(); - if (progress != -1) - { - var eqs = ""; - for (var I = 0; I < progress / 10; ++I) - eqs += "="; - var dshs = ""; - for (var I = 0; I < 10 - (progress / 10); ++I) - eqs += "-"; - OutputProc(String.Format("Progress: [{0}{1}] {2}%", eqs, dshs, progress)); - } - } - return ExitCode.Normal; - } - public override string GetHelpText() - { - return "Shows the busy status of the repo, remote, branch, and HEAD information"; - } - } - - class RepoResetCommand : ConsoleCommand - { - public RepoResetCommand() - { - Keyword = "reset"; - } - protected override ExitCode Run(IList parameters) - { - var result = Instance.Repository.Reset(parameters.Count > 0 && parameters[0].ToLower() == "--origin"); - OutputProc(result ?? "Success!"); - return result == null ? ExitCode.Normal : ExitCode.ServerError; - } - public override string GetArgumentString() - { - return "[--origin]"; - } - public override string GetHelpText() - { - return "Hard resets the repo. If a target is specified, the current branch is reset to that branch"; - } - } - - class RepoUpdateCommand : ConsoleCommand - { - public RepoUpdateCommand() - { - Keyword = "update"; - RequiredParameters = 1; - } - protected override ExitCode Run(IList parameters) - { - bool hard; - switch (parameters[0].ToLower()) - { - case "hard": - hard = true; - break; - case "merge": - hard = false; - break; - default: - OutputProc("Invalid parameter: " + parameters[0]); - return ExitCode.BadCommand; - } - var res = Instance.Repository.Update(hard); - OutputProc(res ?? "Success"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - public override string GetHelpText() - { - return "Updates the current branch the repo is on either via a merge or hard reset"; - } - public override string GetArgumentString() - { - return ""; - } - } - class RepoGenChangelogCommand : ConsoleCommand - { - public RepoGenChangelogCommand() - { - Keyword = "gen-changelog"; - } - protected override ExitCode Run(IList parameters) - { - var result = Instance.Repository.GenerateChangelog(out string error); - OutputProc(error ?? "Success!"); - if (result != null) - OutputProc(result); - return error == null ? ExitCode.Normal : ExitCode.ServerError; - } - - public override string GetHelpText() - { - return "Compiles the html changelog"; - } - } - class RepoPushChangelogCommand : ConsoleCommand - { - public RepoPushChangelogCommand() - { - Keyword = "push-changelog"; - } - protected override ExitCode Run(IList parameters) - { - var result = Instance.Repository.SynchronizePush(); - if(result != null) - OutputProc(result); - return result == null ? ExitCode.Normal : ExitCode.ServerError; - } - - public override string GetHelpText() - { - return "Pushes the html changelog if the SSH authentication is configured correctly"; - } - } - class RepoSetEmailCommand : ConsoleCommand - { - public RepoSetEmailCommand() - { - Keyword = "set-email"; - RequiredParameters = 1; - } - protected override ExitCode Run(IList parameters) - { - Instance.Repository.SetCommitterEmail(parameters[0]); - return ExitCode.Normal; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Set the e-mail used for commits"; - } - } - class RepoSetNameCommand : ConsoleCommand - { - public RepoSetNameCommand() - { - Keyword = "set-name"; - RequiredParameters = 1; - } - protected override ExitCode Run(IList parameters) - { - Instance.Repository.SetCommitterName(parameters[0]); - return ExitCode.Normal; - } - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Set the name used for commits"; - } - } - - class RepoMergePRCommand : ConsoleCommand - { - public RepoMergePRCommand() - { - Keyword = "merge-pr"; - RequiredParameters = 1; - } - - protected override ExitCode Run(IList parameters) - { - ushort PR; - try - { - PR = Convert.ToUInt16(parameters[0]); - } - catch - { - OutputProc("Invalid PR Number!"); - return ExitCode.BadCommand; - } - var res = Instance.Repository.MergePullRequest(PR); - OutputProc(res ?? "Success"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - - public override string GetArgumentString() - { - return ""; - } - public override string GetHelpText() - { - return "Merge the given pull request from the origin repository into the current branch. Only supported with github remotes"; - } - } - - class RepoListPRsCommand : ConsoleCommand - { - public RepoListPRsCommand() - { - Keyword = "list-prs"; - } - public override string GetHelpText() - { - return "Lists currently merge pull requests"; - } - protected override ExitCode Run(IList parameters) - { - var data = Instance.Repository.MergedPullRequests(out string error); - if (data == null) - { - OutputProc(error); - return ExitCode.ServerError; - } - if (data.Count == 0) - OutputProc("None!"); - else - foreach (var I in data) - OutputProc(String.Format("#{0}: {2} by {3} at commit {1}", I.Number, I.Sha, I.Title, I.Author)); - return ExitCode.Normal; - } - } - - class RepoListBackupsCommand : ConsoleCommand - { - public RepoListBackupsCommand() - { - Keyword = "list-backups"; - } - public override string GetHelpText() - { - return "Lists backup tags created by compilation"; - } - protected override ExitCode Run(IList parameters) - { - var data = Instance.Repository.ListBackups(out string error); - if (data == null) - { - OutputProc(error); - return ExitCode.ServerError; - } - if (data.Count == 0) - OutputProc("None!"); - else - foreach (var I in data) - OutputProc(String.Format("{0} at commit {1}", I.Key, I.Value)); - return ExitCode.Normal; - } - } - class RepoCheckoutCommand : ConsoleCommand - { - public RepoCheckoutCommand() - { - Keyword = "checkout"; - RequiredParameters = 1; - } - public override string GetHelpText() - { - return "Checks out the targeted object"; - } - protected override ExitCode Run(IList parameters) - { - var res = Instance.Repository.Checkout(parameters[0]); - OutputProc(res ?? "Success"); - return res == null ? ExitCode.Normal : ExitCode.ServerError; - } - } -} diff --git a/legacy/V3/TGS.CommandLine/RootCommands.cs b/legacy/V3/TGS.CommandLine/RootCommands.cs deleted file mode 100644 index 0de21674cd..0000000000 --- a/legacy/V3/TGS.CommandLine/RootCommands.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Collections.Generic; -using TGS.Interface; - -namespace TGS.CommandLine -{ - class CLICommand : RootCommand - { - public CLICommand(IClient I) - { - var tmp = new List { new UpdateCommand(), new TestmergeCommand(), new RepoCommand(), new BYONDCommand(), new DMCommand(), new DDCommand(), new ConfigCommand(), new IRCCommand(), new DiscordCommand(), new AutoUpdateCommand(), new SetAutoUpdateCommand() }; - if (ConsoleCommand.Instance.Administration != null) - tmp.Add(new AdminCommand()); - if (ConsoleCommand.Server.Management != null) - tmp.Add(new ServiceCommand()); - Children = tmp.ToArray(); - } - - public override void PrintHelp() - { - OutputProc("/tg/station 13 Server Command Line"); - base.PrintHelp(); - } - } - class AutoUpdateCommand : ConsoleCommand - { - public AutoUpdateCommand() - { - Keyword = "auto-update"; - } - - public override string GetHelpText() - { - return "Get the interval in minutes that the server automatically updates"; - } - - protected override ExitCode Run(IList parameters) - { - var res = Instance.Repository.AutoUpdateInterval(); - OutputProc(res == 0 ? "OFF" : String.Format("Auto updating every {0} minutes", res)); - return ExitCode.Normal; - } - } - class SetAutoUpdateCommand : ConsoleCommand - { - public SetAutoUpdateCommand() - { - Keyword = "set-auto-update"; - RequiredParameters = 1; - } - - public override string GetArgumentString() - { - return ""; - } - - public override string GetHelpText() - { - return "Set the interval in minutes that the server automatically updates"; - } - - protected override ExitCode Run(IList parameters) - { - ulong NewInterval; - if (parameters[0].ToLower() == "off") - NewInterval = 0; - else - try - { - NewInterval = Convert.ToUInt64(parameters[0]); - } - catch - { - OutputProc("Invalid interval specified!"); - return ExitCode.BadCommand; - } - - Instance.Repository.SetAutoUpdateInterval(NewInterval); - return ExitCode.Normal; - } - - } - class UpdateCommand : ConsoleCommand - { - public UpdateCommand() - { - Keyword = "update"; - RequiredParameters = 1; - } - protected override ExitCode Run(IList parameters) - { - var gen_cl = parameters.Count > 1 && parameters[1].ToLower() == "--cl"; - var Repo = Instance.Repository; - switch (parameters[0].ToLower()) - { - case "hard": - var res = Repo.Update(true); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - break; - case "merge": - res = Repo.Update(false); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - break; - default: - OutputProc("Please specify hard or merge"); - return ExitCode.BadCommand; - } - if (gen_cl) - { - Repo.GenerateChangelog(out string res); - if (res != null) - OutputProc(res); - else - { - res = Repo.SynchronizePush(); - if (res != null) - OutputProc(res); - } - } - var resu = Instance.Compiler.Compile(true); - OutputProc(resu ? "Compilation started!" : "Compilation could not be started!"); - return resu ? ExitCode.Normal : ExitCode.ServerError; - } - - public override string GetArgumentString() - { - return " [--cl]"; - } - - public override string GetHelpText() - { - return "Updates the server fully, optionally generating and pushing a changelog. Runs asynchronously once compilation starts"; - } - } - - class TestmergeCommand : ConsoleCommand - { - public TestmergeCommand() - { - Keyword = "testmerge"; - RequiredParameters = 1; - } - protected override ExitCode Run(IList parameters) - { - ushort tm; - try - { - tm = Convert.ToUInt16(parameters[0]); - if (tm == 0) - throw new Exception(); - } - catch - { - OutputProc("Invalid tesmerge #: " + parameters[0]); - return ExitCode.BadCommand; - } - var Repo = Instance.Repository; - var res = Repo.MergePullRequest(tm); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - Repo.GenerateChangelog(out res); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - var resu = Instance.Compiler.Compile(true); - OutputProc(resu ? "Compilation started!" : "Compilation could not be started!"); - return resu ? ExitCode.Normal : ExitCode.ServerError; - } - public override string GetArgumentString() - { - return ""; - } - - public override string GetHelpText() - { - return "Merges the specified pull request and updates the server"; - } - } -} diff --git a/legacy/V3/TGS.CommandLine/ServiceCommands.cs b/legacy/V3/TGS.CommandLine/ServiceCommands.cs deleted file mode 100644 index d1a3274d60..0000000000 --- a/legacy/V3/TGS.CommandLine/ServiceCommands.cs +++ /dev/null @@ -1,434 +0,0 @@ -using System; -using System.Collections.Generic; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.CommandLine -{ - /// - /// Used for managing the components - /// - class ServiceCommand : RootCommand - { - /// - /// Construct a - /// - public ServiceCommand() - { - Keyword = "service"; - Children = new Command[] { new ServiceCreateInstanceCommand(), new ServiceDetachInstanceCommand(), new ServiceEnableInstanceCommand(), new ServiceImportInstanceCommand(), new ServiceListInstancesCommand(), new ServicePythonPathCommand(), new ServiceSetPythonPathCommand(), new ServiceSetRemoteAccessPortCommand(), new ServiceRemoteAccessPortCommand(), new ServiceDisableInstanceCommand(), new ServiceRenameInstanceCommand() }; - } - - /// - public override string GetHelpText() - { - return "Manage service wide settings"; - } - } - - /// - /// Command for calling - /// - class ServiceCreateInstanceCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceCreateInstanceCommand() - { - Keyword = "create-instance"; - RequiredParameters = 2; - } - - /// - public override string GetHelpText() - { - return "Creates a new instance at the given path"; - } - - /// - public override string GetArgumentString() - { - return " "; - } - - /// - protected override ExitCode Run(IList parameters) - { - var res = Server.InstanceManager.CreateInstance(parameters[0], parameters[1]); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - /// - /// Command for calling - /// - class ServiceListInstancesCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceListInstancesCommand() - { - Keyword = "list-instances"; - } - - /// - public override string GetHelpText() - { - return "Lists all instances"; - } - - /// - protected override ExitCode Run(IList parameters) - { - foreach (var I in Server.Instances) - OutputProc(I.ToString()); - return ExitCode.Normal; - } - } - - /// - /// Command for calling - /// - class ServiceDetachInstanceCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceDetachInstanceCommand() - { - Keyword = "detach-instance"; - RequiredParameters = 1; - } - - /// - public override string GetHelpText() - { - return "Detaches an instance"; - } - - /// - public override string GetArgumentString() - { - return ""; - } - - /// - protected override ExitCode Run(IList parameters) - { - var res = Server.InstanceManager.DetachInstance(parameters[0]); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - /// - /// Command for calling - /// - class ServiceImportInstanceCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceImportInstanceCommand() - { - Keyword = "import-instance"; - RequiredParameters = 1; - } - - /// - public override string GetHelpText() - { - return "Imports an instance (Chat settings will be lost if they are from a different windows installation)"; - } - - /// - public override string GetArgumentString() - { - return ""; - } - - /// - protected override ExitCode Run(IList parameters) - { - var res = Server.InstanceManager.ImportInstance(parameters[0]); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - /// - /// Command for calling - /// - class ServicePythonPathCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServicePythonPathCommand() - { - Keyword = "python"; - } - - /// - public override string GetHelpText() - { - return "Displays configured path the service uses for python"; - } - - /// - protected override ExitCode Run(IList parameters) - { - var res = Server.Management.PythonPath(); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - /// - /// Command for calling - /// - class ServiceSetPythonPathCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceSetPythonPathCommand() - { - Keyword = "set-python"; - RequiredParameters = 1; - } - - /// - public override string GetHelpText() - { - return "Sets the path to the python installation"; - } - - /// - public override string GetArgumentString() - { - return ""; - } - - /// - protected override ExitCode Run(IList parameters) - { - Server.Management.SetPythonPath(parameters[0]); - return ExitCode.Normal; - } - } - - /// - /// Command for calling with a parameter - /// - class ServiceEnableInstanceCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceEnableInstanceCommand() - { - Keyword = "enable-instance"; - RequiredParameters = 1; - } - - /// - public override string GetHelpText() - { - return "Enables the specified instance"; - } - - /// - public override string GetArgumentString() - { - return ""; - } - - /// - protected override ExitCode Run(IList parameters) - { - var res = Server.InstanceManager.SetInstanceEnabled(parameters[0], true); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - /// - /// Command for calling with a parameter - /// - class ServiceDisableInstanceCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceDisableInstanceCommand() - { - Keyword = "disable-instance"; - RequiredParameters = 1; - } - - /// - public override string GetHelpText() - { - return "Disables the specified instance"; - } - - /// - public override string GetArgumentString() - { - return ""; - } - - /// - protected override ExitCode Run(IList parameters) - { - var res = Server.InstanceManager.SetInstanceEnabled(parameters[0], false); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } - - /// - /// Command for calling - /// - class ServiceRemoteAccessPortCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceRemoteAccessPortCommand() - { - Keyword = "port"; - } - - /// - public override string GetHelpText() - { - return "Displays the service's remote access port"; - } - - /// - protected override ExitCode Run(IList parameters) - { - OutputProc(Server.Management.RemoteAccessPort().ToString()); - return ExitCode.Normal; - } - } - - /// - /// Command for calling - /// - class ServiceSetRemoteAccessPortCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceSetRemoteAccessPortCommand() - { - Keyword = "set-port"; - RequiredParameters = 1; - } - - /// - public override string GetHelpText() - { - return "Sets the service's remote access port"; - } - - /// - public override string GetArgumentString() - { - return ""; - } - - /// - protected override ExitCode Run(IList parameters) - { - ushort port; - try - { - port = Convert.ToUInt16(parameters[0]); - } - catch - { - OutputProc("Invalid port number!"); - return ExitCode.BadCommand; - } - - var res = Server.Management.SetRemoteAccessPort(port); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - OutputProc("Change will be applied after service restart"); - return ExitCode.Normal; - } - } - - /// - /// Command for calling - /// - class ServiceRenameInstanceCommand : ConsoleCommand - { - /// - /// Construct a - /// - public ServiceRenameInstanceCommand() - { - Keyword = "rename-instance"; - RequiredParameters = 1; - } - - /// - public override string GetHelpText() - { - return "Renames an instance. Will temporarily disable the instance if it is active"; - } - - /// - public override string GetArgumentString() - { - return " "; - } - - /// - protected override ExitCode Run(IList parameters) - { - var res = Server.InstanceManager.RenameInstance(parameters[0], parameters[1]); - if (res != null) - { - OutputProc(res); - return ExitCode.ServerError; - } - return ExitCode.Normal; - } - } -} diff --git a/legacy/V3/TGS.CommandLine/TGS.CommandLine.csproj b/legacy/V3/TGS.CommandLine/TGS.CommandLine.csproj deleted file mode 100644 index d0369c4c5d..0000000000 --- a/legacy/V3/TGS.CommandLine/TGS.CommandLine.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Debug - AnyCPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0} - Exe - TGS.CommandLine - TGCommandLine - v4.5.2 - 512 - true - - - - - - tgs.ico - - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\Release\ - TRACE - bin\x86\Release\TGS.CommandLine.xml - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - false - - - - - - - - - - - - - - - - - - - - - - {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} - TGS.Interface - - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.CommandLine/tgs.ico b/legacy/V3/TGS.CommandLine/tgs.ico deleted file mode 100644 index 6ed69fadfcdf8deb049f66dfffc46d5361332feb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123198 zcmeHQ2b>f|_HG1(QxxyiJH1mu&xnDbAQ>bqf<%dugM>v;q9P!>AR-bJ0TGd8IK_aX z2#5hz#f;%jFx}~`r~lK_Logl)QvdI(nVPNX?V0K7shQnbrhdPk?y0V>diCn-SFc{Z zs&<@mPDSUKV;r2TJ0mJMP7lX%PB zX4{+n+bBCg5VM}LHT8#F$viu1R(UX^)tZ+V_Gx?kJFQ0BSK&->jo zecN3mM)kc^4DH@rEW2%nSTJ*<7}B+=xUyR_F|boZaasN9&s zh;pEBOrtZae1USrH3Qp=F@rjaZ~pUd@%5MgC$2(XU7MdHx?WO4^lDQ}4DZoGG&=Lh zFD||4PhcZ@>PM_~5;_BpuAV zX^pCa&+yq^IW>AHEX@4(t~{e7{E={PkDy%f5YL+>p-V z=E)-kcp&&p&(<|hYH`8w%fY7uqx!a$&pfx`(a`hXe+Tl4!vB8$NsR2%Myy_P3+mTF z@EJY}IzDB+pIEa}CVuv*N1~qpcGuTp@hy|Z^;ca1`kRQ4Klqzi|Hx{#3G47_t6G1! zsotp-53=qI&&7SdsrcY;Z^=6F89v+Q!aokh_5S?vgFT;@k@%7H_iim7dtjNEdEGF< zXZURE+9%vhoDAsHKz#c555)5u!}a&z-`K&O#KM~=$@=jbKHI**sTVapr|MSLXKvm# zV$QUy#o)`E2-SY$(0=b|?Kh%VOVQ}8D&T+Bt-#Vnt!kg(bZTA6OsOvHqVc1Fuzh z{sHB#0?vkYYJA42mz;O3ELZ;l9-N#yCsqPKX5n4?>XP%1U5oP8qOM;7Z?n)|l@O;t z;uKbLLOVhJ*Zn|!G1+-^WT0Wa7P(}^v+^9;2hNn$Ko-4qU>EQ3(;Ne%~DG%`I zYs4+*J*Mf<@YHDoJ2$M-&n4g7W!*M&qEiig1AYcVZtMk?iD#K7+f4nhTRjx7{xdCP z*FZjd>x}X4Y~}xX)5kfLaW$}0gY$utop?te_ZkS)N1)9`>pCY&oZmWQoQxaLv4MPd z`n9ho+Fe|Y*xEUuWBv0FM;^>eg_U>Ac0T^_y>h+V);SeqD&JjGW9&nkMb^QqW?m=#R0PIhM>^pVG+pm|qYkr^7X0JG9XQ?-YgR6j@LeZ@SP_Z4kwIpARtbg^iE>Mf@GIRE3>?eG_yaE9TrVTJoxHwF;CmKcd|l zUD2ktYzDN?7+~Pni>j3uQ?KqTAN}vw#q(Er>Svih+MoU5v@!h!^I%@>Ep=EXdjH~P37$j0w4Mw{S$5U&($PDp=L{=`4< zdHYS*iq~Iy4tbm^`OiGNwKz8idR`0Q^9$9+khfCSke7bnFZP{$Z~8z-RPvU2GVd3k z+i)0k)SOToRHB|JmMxee=H-u}o*=G*?xf_y%Uhoo4Iyv5K?ikO)I8n+*6N~8pLT9?hL{YUitorpr<9lSLdmyJ|Ng$rgE9^Mi>G`;-}7nD zoSJnZ`_NA{mhu5I*4elBf93EV-i^Lm>Z3CzUMY3AJLXIkKmG8%WI1uO`H3}R`h+23 z9CRXGK2ygdO_yA7+}-WBS5Sfd~#}4j@qmx*% zXqLEf;!yN6^(EffztsP8C1QR*^Kk1DETivbb)6xXH#ru38wI|vY<}+VUPYVB_m1x) z56V{_MgJ?3=AXdtSHZ89$a~b_E{%_cE~UsWcE0D%xz6j`o-c>GABE?}W6bdbvBd*z z%ew7X{~Mfs#1Fv9_~AWU9<}MQzm!`zYf`cNVnqN)4#o}@(Kk&;-}(*ut98IxE_AS5 z=vwR0R^NaR)6t(*#P}yxHjbDsoV`vpdF*kjI{DC++(*oZwHpOad51@#Q%N4o^PrO_ z$9vV0t~ri~9)*FFB{pip4h{KIQ7Zai2B0 zIkI;fnH=N3Gogn!zj{FX+IY`S2A*m{p3Z~*`n76LwB>(+uZ4ZuU09Q8n3m7*S!Oc4 zXA6^}Wm@2$xUG)*y@B@Jhqm4YJ*E(4eFJPARAs9(Fm(|9`!`I(w0wro0=LyMMm8#c z(#W39a~szl2Hej>zJfBCvY+p9eICFxuc=S+;C1v7O3P=V56pXJ{lkY{JAzh(#NneI z?>NvUocnY|6wQP0oq$Uv}!TNtW(5;&|z-8cBs^+IIf0l z6yJZh8)L}3#ge%RFTlmi=qGaX#|=9E>i(BSSkb=WY0&Q*4u;%{!WH%9+$&niI@dnQk-GVT{re-` z?E4sdQ~xJz>m);mSK^xRY8{l9J()JwO&S|A`>#f2w_^nLanqdE zw87H(d6FNhe8{Vrs_e<6FUxHgjAoEyB$KXBlH9K-C}yH^}McrcQl z7fa{m$?~ro(L+4_#DjADp~`1jEOf%K%VqyKv|F>YATQoiyeEFpmqqf6RemIa$Hmfl z(<0@+ymb@D9+9?XSeKc@=lZL99RvKXQFZ4#NBbDZWGW<6K6UUtK89)cABkIZ1sJ;E-cz+I`e} z$TC@W$Hu4Mt$0saP8mbnpdJ#H&oK_i&unA7-+0H4kk`v^pNa9wW;_x3Gf0PZ( zQ`M1Wb!`0Sxh(tjm!7TIw(iMm!g~49KbSXcscnHP2lFPfZ1ppJ{168^U3t8h_mTh9 z88}|gy`r@oi&Fp08#^$neD=GX%b;9=&PF@DhyFEH&d-oW=Dp?Vb>*i|xKb4xb%xGE z8*X2`p9iQ*!Oke4tBLv3$4R}C??vG!A$tt0q5u2^_9u?Zsf+6R zCv|t^eSmqhj93A7DB2uPhiv>8vC4Mf!E4qH{kG)A;@Oi0=O$n~mNs5qvrNvJP#4$r zZJmAl93{^pak!8E?|b0!dg%K#t{Kq&xA!l*wfwxi#N|c^&@RAZr+dqDDlh zXn4kv`yoTVM4NmGT_R#zK>YWJ8x0#^1;}+Rr{I^cH*kDV0lNMT$os#L-J3Dq&BYvO zEzDolLRq<&HK@KBH2$|$?LXYmw|%_|u(A6pQ0%S7*%NWlaqhSRF%U<@PH}qjb_!${H(*ZLS$NZR6(C4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl - - - - -
-
-
- - - - - - - - - - 0 - - - 0 - - - True - - - 0 - - - False - - - - - - - - - - - 0 - - - - - https://github.com/tgstation/tgstation.git - - - tgstation - - - 2337 - - - master - - - False - - - C:\Program Files (x86)\BYOND\bin - - - tgstation-server - - - tgstation-server@users.noreply.github.com - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/ControlPanel/ByondPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ByondPage.cs deleted file mode 100644 index f5ec5546d7..0000000000 --- a/legacy/V3/TGS.ControlPanel/ControlPanel/ByondPage.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Windows.Forms; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.ControlPanel -{ - partial class ControlPanel - { - string lastReadError = null; - void InitBYONDPage() - { - var BYOND = Instance.Byond; - var CV = BYOND.GetVersion(ByondVersion.Installed); - if (CV == null) - CV = BYOND.GetVersion(ByondVersion.Staged); - if (CV != null) - { - var splits = CV.Split('.'); - if(splits.Length == 2) - { - try - { - var Major = Convert.ToInt32(splits[0]); - var Minor = Convert.ToInt32(splits[1]); - MajorVersionNumeric.Value = Major; - MinorVersionNumeric.Value = Minor; - } - catch { } - } - } - - var latestVer = BYOND.GetVersion(ByondVersion.Latest); - LatestVersionLabel.Text = latestVer; - - try - { - var splits = latestVer.Split('.'); - var maj = Convert.ToInt32(splits[0]); - MinorVersionNumeric.Value = Convert.ToInt32(splits[1]); - MajorVersionNumeric.Value = maj; - } - catch { } - } - private void UpdateButton_Click(object sender, EventArgs e) - { - UpdateBYONDButtons(); - if (!Instance.Byond.UpdateToVersion((int)MajorVersionNumeric.Value, (int)MinorVersionNumeric.Value)) - MessageBox.Show("Unable to begin update, there is another operation in progress."); - } - - private void BYONDRefreshButton_Click(object sender, EventArgs e) - { - UpdateBYONDButtons(); - } - void UpdateBYONDButtons() - { - var BYOND = Instance.Byond; - - VersionLabel.Text = BYOND.GetVersion(ByondVersion.Installed) ?? "Not Installed"; - - StagedVersionTitle.Visible = false; - StagedVersionLabel.Visible = false; - switch (BYOND.CurrentStatus()) - { - case ByondStatus.Idle: - case ByondStatus.Starting: - StatusLabel.Text = "Idle"; - UpdateButton.Enabled = true; - break; - case ByondStatus.Downloading: - StatusLabel.Text = "Downloading..."; - UpdateButton.Enabled = false; - break; - case ByondStatus.Staging: - StatusLabel.Text = "Staging..."; - UpdateButton.Enabled = false; - break; - case ByondStatus.Staged: - StagedVersionTitle.Visible = true; - StagedVersionLabel.Visible = true; - StagedVersionLabel.Text = BYOND.GetVersion(ByondVersion.Staged) ?? "Unknown"; - StatusLabel.Text = "Staged and waiting for BYOND to shutdown..."; - UpdateButton.Enabled = true; - break; - case ByondStatus.Updating: - StatusLabel.Text = "Applying update..."; - UpdateButton.Enabled = false; - break; - } - var error = Instance.Byond.GetError(); - if (error != lastReadError) - { - lastReadError = error; - if (error != null) - MessageBox.Show("An error occurred: " + lastReadError); - } - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/ControlPanel/ChatPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ChatPage.cs deleted file mode 100644 index d512d0d6ef..0000000000 --- a/legacy/V3/TGS.ControlPanel/ControlPanel/ChatPage.cs +++ /dev/null @@ -1,218 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Windows.Forms; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.ControlPanel -{ - partial class ControlPanel - { - bool updatingChat = false; - - ChatProvider ModifyingProvider - { - get { return (ChatProvider)Properties.Settings.Default.LastChatProvider; } - set { Properties.Settings.Default.LastChatProvider = (int)value; } - } - - void LoadChatPage() - { - updatingChat = true; - var Chat = Instance.Chat; - var PI = Chat.ProviderInfos()[(int)ModifyingProvider]; - ChatAdminsTextBox.Visible = true; - IRCModesComboBox.Visible = false; - switch (ModifyingProvider) - { - case ChatProvider.Discord: - var DPI = new DiscordSetupInfo(PI); - DiscordProviderSwitch.Select(); - AuthField1.Text = DPI.BotToken; //it's invisible so whatever - AuthField1Title.Text = "Bot Token:"; - AuthField2.Visible = false; - AuthField2Title.Visible = false; - ChatServerText.Visible = false; - ChatPortSelector.Visible = false; - ChatServerTitle.Visible = false; - ChatPortTitle.Visible = false; - ChatNicknameText.Visible = false; - ChatNicknameTitle.Visible = false; - - ChatAdminsTitle.Text = String.Format("Admin {0} IDs:", DPI.AdminsAreSpecial ? "Role" : "User"); - ChannelsTitle.Text = "Broadcast/Listening Channel IDs:"; - AdminModeNormal.Text = "User IDs"; - AdminModeSpecial.Text = "Role IDs"; - break; - case ChatProvider.IRC: - var IRC = new IRCSetupInfo(PI); - IRCProviderSwitch.Select(); - AuthField1.Text = IRC.AuthTarget; - AuthField2.Text = IRC.AuthMessage; - AuthField2.Visible = true; - AuthField2Title.Visible = true; - AuthField1Title.Text = "Auth Target:"; - AuthField2Title.Text = "Auth Message:"; - ChatServerText.Visible = true; - ChatPortSelector.Visible = true; - ChatServerTitle.Visible = true; - ChatPortTitle.Visible = true; - ChatServerText.Text = IRC.URL; - ChatPortSelector.Value = IRC.Port; - ChatNicknameText.Visible = true; - ChatNicknameTitle.Visible = true; - ChatNicknameText.Text = IRC.Nickname; - ChatAdminsTitle.Text = String.Format("Admin {0}:", IRC.AdminsAreSpecial ? "Req Mode" : "Nicknames"); - ChannelsTitle.Text = "Broadcast/Listening Channels:"; - AdminModeNormal.Text = "Nicknames"; - AdminModeSpecial.Text = "Channel Mode"; - if (IRC.AdminsAreSpecial) - { - ChatAdminsTextBox.Visible = false; - IRCModesComboBox.Visible = true; - IRCModesComboBox.SelectedIndex = (int)IRC.AuthLevel; - } - break; - default: - Properties.Settings.Default.LastChatProvider = (int)ChatProvider.IRC; - LoadChatPage(); - return; - } - - AdminModeNormal.Checked = !PI.AdminsAreSpecial; - AdminModeSpecial.Checked = PI.AdminsAreSpecial; - ChatEnabledCheckbox.Checked = PI.Enabled; - if (!PI.Enabled) - ChatStatusLabel.Text = "Disabled"; - else if (Chat.Connected(ModifyingProvider)) - ChatStatusLabel.Text = "Connected"; - else - ChatStatusLabel.Text = "Disconnected"; - ChatReconnectButton.Enabled = PI.Enabled; - - AssignListToTextbox(PI.AdminList, ChatAdminsTextBox); - AssignListToTextbox(PI.WatchdogChannels, WDChannelsTextbox); - AssignListToTextbox(PI.AdminChannels, AdminChannelsTextbox); - AssignListToTextbox(PI.DevChannels, DevChannelsTextbox); - AssignListToTextbox(PI.GameChannels, GameChannelsTextbox); - updatingChat = false; - } - - static void AssignListToTextbox(IList a, TextBox b) - { - b.Text = ""; - foreach (var I in a) - b.Text += I + Environment.NewLine; - } - - private void ChatRefreshButton_Click(object sender, EventArgs e) - { - LoadChatPage(); - } - - private void ChatReconnectButton_Click(object sender, EventArgs e) - { - Instance.Chat.Reconnect(ModifyingProvider); - LoadChatPage(); - } - - static string[] SplitByLine(TextBox t) - { - var channels = t.Text.Split('\n'); - - var finalChannels = new List(); - foreach (var I in channels) - { - var trimmed = I.Trim(); - if(trimmed != "") - finalChannels.Add(trimmed); - } - return finalChannels.ToArray(); - } - - private void DiscordProviderSwitch_CheckedChanged(object sender, EventArgs e) - { - if (!updatingChat && DiscordProviderSwitch.Checked) - { - ModifyingProvider = ChatProvider.Discord; - LoadChatPage(); - } - } - - private void IRCProviderSwitch_CheckedChanged(object sender, EventArgs e) - { - if (!updatingChat && IRCProviderSwitch.Checked) - { - ModifyingProvider = ChatProvider.IRC; - LoadChatPage(); - } - } - void SetAdminsAreSpecial(bool value) - { - var Chat = Instance.Chat; - var PI = Chat.ProviderInfos()[(int)ModifyingProvider]; - PI.AdminsAreSpecial = value; - var res = Chat.SetProviderInfo(PI); - if (res != null) - MessageBox.Show(res); - LoadChatPage(); - } - - private void AdminModeNormal_CheckedChanged(object sender, EventArgs e) - { - if (!updatingChat && AdminModeNormal.Checked) - SetAdminsAreSpecial(false); - } - - private void AdminModeSpecial_CheckedChanged(object sender, EventArgs e) - { - if (!updatingChat && AdminModeSpecial.Checked) - SetAdminsAreSpecial(true); - } - - private void ChatApplyButton_Click(object sender, EventArgs e) - { - string res = null; - ChatSetupInfo wip = null; - switch (ModifyingProvider) - { - case ChatProvider.Discord: - wip = new DiscordSetupInfo() - { - BotToken = AuthField1.Text - }; - break; - case ChatProvider.IRC: - wip = new IRCSetupInfo() - { - AuthMessage = AuthField2.Text, - AuthTarget = AuthField1.Text, - Nickname = ChatNicknameText.Text, - URL = ChatServerText.Text, - Port = (ushort)ChatPortSelector.Value, - AuthLevel = (IRCMode)IRCModesComboBox.SelectedIndex, - }; - break; - default: - res = "You really shouldn't be able to read this."; - break; - } - - if (res == null) - { - wip.AdminChannels = new List(AdminChannelsTextbox.Text.Split(Environment.NewLine.ToCharArray())); - wip.WatchdogChannels = new List(WDChannelsTextbox.Text.Split(Environment.NewLine.ToCharArray())); - wip.DevChannels = new List(DevChannelsTextbox.Text.Split(Environment.NewLine.ToCharArray())); - wip.GameChannels = new List(GameChannelsTextbox.Text.Split(Environment.NewLine.ToCharArray())); - wip.AdminList = new List(ChatAdminsTextBox.Text.Split(Environment.NewLine.ToCharArray())); - wip.Enabled = ChatEnabledCheckbox.Checked; - wip.AdminsAreSpecial = AdminModeSpecial.Checked; - - res = Instance.Chat.SetProviderInfo(wip); - } - if (res != null) - MessageBox.Show(res); - LoadChatPage(); - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.Designer.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.Designer.cs deleted file mode 100644 index ba5d05108f..0000000000 --- a/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.Designer.cs +++ /dev/null @@ -1,1882 +0,0 @@ -namespace TGS.ControlPanel -{ - partial class ControlPanel - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - Cleanup(); - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ControlPanel)); - this.RepoBGW = new System.ComponentModel.BackgroundWorker(); - this.ServerStartBGW = new System.ComponentModel.BackgroundWorker(); - this.ChatPanel = new System.Windows.Forms.TabPage(); - this.label5 = new System.Windows.Forms.Label(); - this.label4 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.IRCModesComboBox = new System.Windows.Forms.ComboBox(); - this.label1 = new System.Windows.Forms.Label(); - this.AdminChannelsTextbox = new System.Windows.Forms.TextBox(); - this.WDChannelsTextbox = new System.Windows.Forms.TextBox(); - this.GameChannelsTextbox = new System.Windows.Forms.TextBox(); - this.DevChannelsTextbox = new System.Windows.Forms.TextBox(); - this.ChatNicknameText = new System.Windows.Forms.TextBox(); - this.ChatServerText = new System.Windows.Forms.TextBox(); - this.AuthField2 = new System.Windows.Forms.TextBox(); - this.AuthField1 = new System.Windows.Forms.TextBox(); - this.ChatAdminsTextBox = new System.Windows.Forms.TextBox(); - this.ChatRefreshButton = new System.Windows.Forms.Button(); - this.ChatNicknameTitle = new System.Windows.Forms.Label(); - this.ChatPortSelector = new System.Windows.Forms.NumericUpDown(); - this.ChatPortTitle = new System.Windows.Forms.Label(); - this.ChatServerTitle = new System.Windows.Forms.Label(); - this.ChatApplyButton = new System.Windows.Forms.Button(); - this.AuthField2Title = new System.Windows.Forms.Label(); - this.AuthField1Title = new System.Windows.Forms.Label(); - this.ChatReconnectButton = new System.Windows.Forms.Button(); - this.ChatStatusLabel = new System.Windows.Forms.Label(); - this.ChatStatusTitle = new System.Windows.Forms.Label(); - this.ChatEnabledCheckbox = new System.Windows.Forms.CheckBox(); - this.ChatProviderSelectorPanel = new System.Windows.Forms.Panel(); - this.DiscordProviderSwitch = new System.Windows.Forms.RadioButton(); - this.IRCProviderSwitch = new System.Windows.Forms.RadioButton(); - this.ChatProviderTitle = new System.Windows.Forms.Label(); - this.ChannelsTitle = new System.Windows.Forms.Label(); - this.ChatAdminsTitle = new System.Windows.Forms.Label(); - this.panel1 = new System.Windows.Forms.Panel(); - this.AdminModeSpecial = new System.Windows.Forms.RadioButton(); - this.AdminModeNormal = new System.Windows.Forms.RadioButton(); - this.ServerPanel = new System.Windows.Forms.TabPage(); - this.AutoUpdateMLabel = new System.Windows.Forms.Label(); - this.AutoUpdateInterval = new System.Windows.Forms.NumericUpDown(); - this.AutoUpdateCheckbox = new System.Windows.Forms.CheckBox(); - this.ServerPageRefreshButton = new System.Windows.Forms.Button(); - this.AutostartCheckbox = new System.Windows.Forms.CheckBox(); - this.WebclientCheckBox = new System.Windows.Forms.CheckBox(); - this.WorldAnnounceButton = new System.Windows.Forms.Button(); - this.WorldAnnounceField = new System.Windows.Forms.TextBox(); - this.projectNameText = new System.Windows.Forms.TextBox(); - this.WorldAnnounceLabel = new System.Windows.Forms.Label(); - this.SecuritySelector = new System.Windows.Forms.ComboBox(); - this.SecurityTitle = new System.Windows.Forms.Label(); - this.RemoveAllTestMergesButton = new System.Windows.Forms.Button(); - this.ServerPathLabel = new System.Windows.Forms.Label(); - this.CompileCancelButton = new System.Windows.Forms.Button(); - this.ProjectPathLabel = new System.Windows.Forms.Label(); - this.PortLabel = new System.Windows.Forms.Label(); - this.PortSelector = new System.Windows.Forms.NumericUpDown(); - this.TestMergeManagerButton = new System.Windows.Forms.Button(); - this.UpdateServerButton = new System.Windows.Forms.Button(); - this.ServerGRestartButton = new System.Windows.Forms.Button(); - this.ServerGStopButton = new System.Windows.Forms.CheckBox(); - this.ServerRestartButton = new System.Windows.Forms.Button(); - this.ServerStopButton = new System.Windows.Forms.Button(); - this.ServerStartButton = new System.Windows.Forms.Button(); - this.CompilerStatusLabel = new System.Windows.Forms.Label(); - this.CompilerLabel = new System.Windows.Forms.Label(); - this.compileButton = new System.Windows.Forms.Button(); - this.initializeButton = new System.Windows.Forms.Button(); - this.ServerStatusLabel = new System.Windows.Forms.Label(); - this.ServerStatusTitle = new System.Windows.Forms.Label(); - this.BYONDPanel = new System.Windows.Forms.TabPage(); - this.BYONDRefreshButton = new System.Windows.Forms.Button(); - this.LatestVersionLabel = new System.Windows.Forms.Label(); - this.LatestVersionTitle = new System.Windows.Forms.Label(); - this.StagedVersionLabel = new System.Windows.Forms.Label(); - this.StagedVersionTitle = new System.Windows.Forms.Label(); - this.StatusLabel = new System.Windows.Forms.Label(); - this.VersionLabel = new System.Windows.Forms.Label(); - this.VersionTitle = new System.Windows.Forms.Label(); - this.MinorVersionLabel = new System.Windows.Forms.Label(); - this.MajorVersionLabel = new System.Windows.Forms.Label(); - this.UpdateButton = new System.Windows.Forms.Button(); - this.MinorVersionNumeric = new System.Windows.Forms.NumericUpDown(); - this.MajorVersionNumeric = new System.Windows.Forms.NumericUpDown(); - this.RepoPanel = new System.Windows.Forms.TabPage(); - this.SyncCommitsCheckBox = new System.Windows.Forms.CheckBox(); - this.TGSJsonUpdate = new System.Windows.Forms.Button(); - this.RepoRefreshButton = new System.Windows.Forms.Button(); - this.BackupTagsList = new System.Windows.Forms.ListBox(); - this.ResetRemote = new System.Windows.Forms.Button(); - this.RecloneButton = new System.Windows.Forms.Button(); - this.RepoBranchTextBox = new System.Windows.Forms.TextBox(); - this.RepoRemoteTextBox = new System.Windows.Forms.TextBox(); - this.RepoGenChangelogButton = new System.Windows.Forms.Button(); - this.TestmergeSelector = new System.Windows.Forms.NumericUpDown(); - this.TestMergeListLabel = new System.Windows.Forms.ListBox(); - this.CurrentRevisionLabel = new System.Windows.Forms.Label(); - this.RepoApplyButton = new System.Windows.Forms.Button(); - this.HardReset = new System.Windows.Forms.Button(); - this.UpdateRepoButton = new System.Windows.Forms.Button(); - this.MergePRButton = new System.Windows.Forms.Button(); - this.IdentityLabel = new System.Windows.Forms.Label(); - this.TestMergeListTitle = new System.Windows.Forms.Label(); - this.RemoteNameTitle = new System.Windows.Forms.Label(); - this.BranchNameTitle = new System.Windows.Forms.Label(); - this.CurrentRevisionTitle = new System.Windows.Forms.Label(); - this.CloneRepositoryButton = new System.Windows.Forms.Button(); - this.RepoProgressBarLabel = new System.Windows.Forms.Label(); - this.RepoProgressBar = new System.Windows.Forms.ProgressBar(); - this.Panels = new System.Windows.Forms.TabControl(); - this.StaticPanel = new System.Windows.Forms.TabPage(); - this.RecreateStaticButton = new System.Windows.Forms.Button(); - this.StaticFileDownloadButton = new System.Windows.Forms.Button(); - this.StaticFilesRefreshButton = new System.Windows.Forms.Button(); - this.StaticFileUploadButton = new System.Windows.Forms.Button(); - this.StaticFileEditTextbox = new System.Windows.Forms.TextBox(); - this.StaticFileDeleteButton = new System.Windows.Forms.Button(); - this.StaticFileSaveButton = new System.Windows.Forms.Button(); - this.StaticFileCreateButton = new System.Windows.Forms.Button(); - this.label6 = new System.Windows.Forms.Label(); - this.StaticFileListBox = new System.Windows.Forms.ListBox(); - this.ChatPanel.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.ChatPortSelector)).BeginInit(); - this.ChatProviderSelectorPanel.SuspendLayout(); - this.panel1.SuspendLayout(); - this.ServerPanel.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.AutoUpdateInterval)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.PortSelector)).BeginInit(); - this.BYONDPanel.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.MinorVersionNumeric)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.MajorVersionNumeric)).BeginInit(); - this.RepoPanel.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.TestmergeSelector)).BeginInit(); - this.Panels.SuspendLayout(); - this.StaticPanel.SuspendLayout(); - this.SuspendLayout(); - // - // RepoBGW - // - this.RepoBGW.WorkerReportsProgress = true; - this.RepoBGW.WorkerSupportsCancellation = true; - // - // ServerStartBGW - // - this.ServerStartBGW.DoWork += new System.ComponentModel.DoWorkEventHandler(this.ServerStartBGW_DoWork); - // - // ChatPanel - // - this.ChatPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.ChatPanel.Controls.Add(this.label5); - this.ChatPanel.Controls.Add(this.label4); - this.ChatPanel.Controls.Add(this.label3); - this.ChatPanel.Controls.Add(this.label2); - this.ChatPanel.Controls.Add(this.IRCModesComboBox); - this.ChatPanel.Controls.Add(this.label1); - this.ChatPanel.Controls.Add(this.AdminChannelsTextbox); - this.ChatPanel.Controls.Add(this.WDChannelsTextbox); - this.ChatPanel.Controls.Add(this.GameChannelsTextbox); - this.ChatPanel.Controls.Add(this.DevChannelsTextbox); - this.ChatPanel.Controls.Add(this.ChatNicknameText); - this.ChatPanel.Controls.Add(this.ChatServerText); - this.ChatPanel.Controls.Add(this.AuthField2); - this.ChatPanel.Controls.Add(this.AuthField1); - this.ChatPanel.Controls.Add(this.ChatAdminsTextBox); - this.ChatPanel.Controls.Add(this.ChatRefreshButton); - this.ChatPanel.Controls.Add(this.ChatNicknameTitle); - this.ChatPanel.Controls.Add(this.ChatPortSelector); - this.ChatPanel.Controls.Add(this.ChatPortTitle); - this.ChatPanel.Controls.Add(this.ChatServerTitle); - this.ChatPanel.Controls.Add(this.ChatApplyButton); - this.ChatPanel.Controls.Add(this.AuthField2Title); - this.ChatPanel.Controls.Add(this.AuthField1Title); - this.ChatPanel.Controls.Add(this.ChatReconnectButton); - this.ChatPanel.Controls.Add(this.ChatStatusLabel); - this.ChatPanel.Controls.Add(this.ChatStatusTitle); - this.ChatPanel.Controls.Add(this.ChatEnabledCheckbox); - this.ChatPanel.Controls.Add(this.ChatProviderSelectorPanel); - this.ChatPanel.Controls.Add(this.ChatProviderTitle); - this.ChatPanel.Controls.Add(this.ChannelsTitle); - this.ChatPanel.Controls.Add(this.ChatAdminsTitle); - this.ChatPanel.Controls.Add(this.panel1); - this.ChatPanel.Location = new System.Drawing.Point(4, 22); - this.ChatPanel.Name = "ChatPanel"; - this.ChatPanel.Padding = new System.Windows.Forms.Padding(3); - this.ChatPanel.Size = new System.Drawing.Size(868, 366); - this.ChatPanel.TabIndex = 4; - this.ChatPanel.Text = "Chat"; - // - // label5 - // - this.label5.AutoSize = true; - this.label5.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label5.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label5.Location = new System.Drawing.Point(686, 219); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(148, 18); - this.label5.TabIndex = 43; - this.label5.Text = "Game Messages:"; - this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // label4 - // - this.label4.AutoSize = true; - this.label4.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label4.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label4.Location = new System.Drawing.Point(516, 219); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(136, 18); - this.label4.TabIndex = 42; - this.label4.Text = "Developer Info:"; - this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // label3 - // - this.label3.AutoSize = true; - this.label3.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label3.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label3.Location = new System.Drawing.Point(686, 61); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(135, 18); - this.label3.TabIndex = 41; - this.label3.Text = "Administration:"; - this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label2.Location = new System.Drawing.Point(516, 61); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(153, 18); - this.label2.TabIndex = 40; - this.label2.Text = "Server Watchdog:"; - this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // IRCModesComboBox - // - this.IRCModesComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.IRCModesComboBox.FormattingEnabled = true; - this.IRCModesComboBox.Items.AddRange(new object[] { - "Voice - +", - "Halfop - %", - "Op - @", - "Owner - ~"}); - this.IRCModesComboBox.Location = new System.Drawing.Point(369, 170); - this.IRCModesComboBox.Name = "IRCModesComboBox"; - this.IRCModesComboBox.Size = new System.Drawing.Size(141, 21); - this.IRCModesComboBox.TabIndex = 38; - this.IRCModesComboBox.Visible = false; - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label1.Location = new System.Drawing.Point(30, 174); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(110, 18); - this.label1.TabIndex = 39; - this.label1.Text = "Admin Auth:"; - this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // AdminChannelsTextbox - // - this.AdminChannelsTextbox.Location = new System.Drawing.Point(689, 82); - this.AdminChannelsTextbox.Multiline = true; - this.AdminChannelsTextbox.Name = "AdminChannelsTextbox"; - this.AdminChannelsTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.AdminChannelsTextbox.Size = new System.Drawing.Size(167, 120); - this.AdminChannelsTextbox.TabIndex = 37; - // - // WDChannelsTextbox - // - this.WDChannelsTextbox.Location = new System.Drawing.Point(516, 82); - this.WDChannelsTextbox.Multiline = true; - this.WDChannelsTextbox.Name = "WDChannelsTextbox"; - this.WDChannelsTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.WDChannelsTextbox.Size = new System.Drawing.Size(167, 120); - this.WDChannelsTextbox.TabIndex = 36; - // - // GameChannelsTextbox - // - this.GameChannelsTextbox.Location = new System.Drawing.Point(689, 240); - this.GameChannelsTextbox.Multiline = true; - this.GameChannelsTextbox.Name = "GameChannelsTextbox"; - this.GameChannelsTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.GameChannelsTextbox.Size = new System.Drawing.Size(167, 120); - this.GameChannelsTextbox.TabIndex = 35; - // - // DevChannelsTextbox - // - this.DevChannelsTextbox.Location = new System.Drawing.Point(516, 240); - this.DevChannelsTextbox.Multiline = true; - this.DevChannelsTextbox.Name = "DevChannelsTextbox"; - this.DevChannelsTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.DevChannelsTextbox.Size = new System.Drawing.Size(167, 120); - this.DevChannelsTextbox.TabIndex = 34; - // - // ChatNicknameText - // - this.ChatNicknameText.Location = new System.Drawing.Point(174, 251); - this.ChatNicknameText.Name = "ChatNicknameText"; - this.ChatNicknameText.Size = new System.Drawing.Size(151, 20); - this.ChatNicknameText.TabIndex = 31; - // - // ChatServerText - // - this.ChatServerText.Location = new System.Drawing.Point(174, 199); - this.ChatServerText.Name = "ChatServerText"; - this.ChatServerText.Size = new System.Drawing.Size(151, 20); - this.ChatServerText.TabIndex = 27; - // - // AuthField2 - // - this.AuthField2.Location = new System.Drawing.Point(174, 309); - this.AuthField2.Name = "AuthField2"; - this.AuthField2.Size = new System.Drawing.Size(151, 20); - this.AuthField2.TabIndex = 22; - // - // AuthField1 - // - this.AuthField1.Location = new System.Drawing.Point(174, 283); - this.AuthField1.Name = "AuthField1"; - this.AuthField1.Size = new System.Drawing.Size(151, 20); - this.AuthField1.TabIndex = 21; - // - // ChatAdminsTextBox - // - this.ChatAdminsTextBox.Location = new System.Drawing.Point(369, 49); - this.ChatAdminsTextBox.Multiline = true; - this.ChatAdminsTextBox.Name = "ChatAdminsTextBox"; - this.ChatAdminsTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.ChatAdminsTextBox.Size = new System.Drawing.Size(141, 311); - this.ChatAdminsTextBox.TabIndex = 10; - // - // ChatRefreshButton - // - this.ChatRefreshButton.Location = new System.Drawing.Point(174, 335); - this.ChatRefreshButton.Name = "ChatRefreshButton"; - this.ChatRefreshButton.Size = new System.Drawing.Size(112, 25); - this.ChatRefreshButton.TabIndex = 32; - this.ChatRefreshButton.Text = "Refresh"; - this.ChatRefreshButton.UseVisualStyleBackColor = true; - this.ChatRefreshButton.Click += new System.EventHandler(this.ChatRefreshButton_Click); - // - // ChatNicknameTitle - // - this.ChatNicknameTitle.AutoSize = true; - this.ChatNicknameTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ChatNicknameTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChatNicknameTitle.Location = new System.Drawing.Point(30, 250); - this.ChatNicknameTitle.Name = "ChatNicknameTitle"; - this.ChatNicknameTitle.Size = new System.Drawing.Size(94, 18); - this.ChatNicknameTitle.TabIndex = 30; - this.ChatNicknameTitle.Text = "Nickname:"; - this.ChatNicknameTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ChatPortSelector - // - this.ChatPortSelector.Location = new System.Drawing.Point(174, 225); - this.ChatPortSelector.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.ChatPortSelector.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.ChatPortSelector.Name = "ChatPortSelector"; - this.ChatPortSelector.Size = new System.Drawing.Size(151, 20); - this.ChatPortSelector.TabIndex = 29; - this.ChatPortSelector.Value = new decimal(new int[] { - 1, - 0, - 0, - 0}); - // - // ChatPortTitle - // - this.ChatPortTitle.AutoSize = true; - this.ChatPortTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ChatPortTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChatPortTitle.Location = new System.Drawing.Point(30, 227); - this.ChatPortTitle.Name = "ChatPortTitle"; - this.ChatPortTitle.Size = new System.Drawing.Size(48, 18); - this.ChatPortTitle.TabIndex = 28; - this.ChatPortTitle.Text = "Port:"; - this.ChatPortTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ChatServerTitle - // - this.ChatServerTitle.AutoSize = true; - this.ChatServerTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ChatServerTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChatServerTitle.Location = new System.Drawing.Point(30, 201); - this.ChatServerTitle.Name = "ChatServerTitle"; - this.ChatServerTitle.Size = new System.Drawing.Size(66, 18); - this.ChatServerTitle.TabIndex = 26; - this.ChatServerTitle.Text = "Server:"; - this.ChatServerTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ChatApplyButton - // - this.ChatApplyButton.Location = new System.Drawing.Point(56, 335); - this.ChatApplyButton.Name = "ChatApplyButton"; - this.ChatApplyButton.Size = new System.Drawing.Size(112, 25); - this.ChatApplyButton.TabIndex = 25; - this.ChatApplyButton.Text = "Apply"; - this.ChatApplyButton.UseVisualStyleBackColor = true; - this.ChatApplyButton.Click += new System.EventHandler(this.ChatApplyButton_Click); - // - // AuthField2Title - // - this.AuthField2Title.AutoSize = true; - this.AuthField2Title.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.AuthField2Title.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.AuthField2Title.Location = new System.Drawing.Point(30, 311); - this.AuthField2Title.Name = "AuthField2Title"; - this.AuthField2Title.Size = new System.Drawing.Size(45, 18); - this.AuthField2Title.TabIndex = 24; - this.AuthField2Title.Text = "AF2:"; - this.AuthField2Title.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // AuthField1Title - // - this.AuthField1Title.AutoSize = true; - this.AuthField1Title.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.AuthField1Title.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.AuthField1Title.Location = new System.Drawing.Point(30, 285); - this.AuthField1Title.Name = "AuthField1Title"; - this.AuthField1Title.Size = new System.Drawing.Size(45, 18); - this.AuthField1Title.TabIndex = 23; - this.AuthField1Title.Text = "AF1:"; - this.AuthField1Title.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ChatReconnectButton - // - this.ChatReconnectButton.Enabled = false; - this.ChatReconnectButton.Location = new System.Drawing.Point(174, 141); - this.ChatReconnectButton.Name = "ChatReconnectButton"; - this.ChatReconnectButton.Size = new System.Drawing.Size(112, 25); - this.ChatReconnectButton.TabIndex = 18; - this.ChatReconnectButton.Text = "Reconnect"; - this.ChatReconnectButton.UseVisualStyleBackColor = true; - this.ChatReconnectButton.Click += new System.EventHandler(this.ChatReconnectButton_Click); - // - // ChatStatusLabel - // - this.ChatStatusLabel.AutoSize = true; - this.ChatStatusLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ChatStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChatStatusLabel.Location = new System.Drawing.Point(171, 113); - this.ChatStatusLabel.Name = "ChatStatusLabel"; - this.ChatStatusLabel.Size = new System.Drawing.Size(82, 18); - this.ChatStatusLabel.TabIndex = 17; - this.ChatStatusLabel.Text = "Unknown"; - this.ChatStatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ChatStatusTitle - // - this.ChatStatusTitle.AutoSize = true; - this.ChatStatusTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ChatStatusTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChatStatusTitle.Location = new System.Drawing.Point(30, 113); - this.ChatStatusTitle.Name = "ChatStatusTitle"; - this.ChatStatusTitle.Size = new System.Drawing.Size(112, 18); - this.ChatStatusTitle.TabIndex = 16; - this.ChatStatusTitle.Text = "Chat Status:"; - this.ChatStatusTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ChatEnabledCheckbox - // - this.ChatEnabledCheckbox.AutoSize = true; - this.ChatEnabledCheckbox.Font = new System.Drawing.Font("Verdana", 12F); - this.ChatEnabledCheckbox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChatEnabledCheckbox.Location = new System.Drawing.Point(32, 142); - this.ChatEnabledCheckbox.Name = "ChatEnabledCheckbox"; - this.ChatEnabledCheckbox.Size = new System.Drawing.Size(136, 22); - this.ChatEnabledCheckbox.TabIndex = 15; - this.ChatEnabledCheckbox.Text = "Chat Enabled"; - this.ChatEnabledCheckbox.UseVisualStyleBackColor = true; - // - // ChatProviderSelectorPanel - // - this.ChatProviderSelectorPanel.Controls.Add(this.DiscordProviderSwitch); - this.ChatProviderSelectorPanel.Controls.Add(this.IRCProviderSwitch); - this.ChatProviderSelectorPanel.Location = new System.Drawing.Point(23, 49); - this.ChatProviderSelectorPanel.Name = "ChatProviderSelectorPanel"; - this.ChatProviderSelectorPanel.Size = new System.Drawing.Size(302, 50); - this.ChatProviderSelectorPanel.TabIndex = 14; - // - // DiscordProviderSwitch - // - this.DiscordProviderSwitch.AutoSize = true; - this.DiscordProviderSwitch.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.DiscordProviderSwitch.Location = new System.Drawing.Point(220, 17); - this.DiscordProviderSwitch.Name = "DiscordProviderSwitch"; - this.DiscordProviderSwitch.Size = new System.Drawing.Size(61, 17); - this.DiscordProviderSwitch.TabIndex = 13; - this.DiscordProviderSwitch.TabStop = true; - this.DiscordProviderSwitch.Text = "Discord"; - this.DiscordProviderSwitch.UseVisualStyleBackColor = true; - this.DiscordProviderSwitch.CheckedChanged += new System.EventHandler(this.DiscordProviderSwitch_CheckedChanged); - // - // IRCProviderSwitch - // - this.IRCProviderSwitch.AutoSize = true; - this.IRCProviderSwitch.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.IRCProviderSwitch.Location = new System.Drawing.Point(14, 17); - this.IRCProviderSwitch.Name = "IRCProviderSwitch"; - this.IRCProviderSwitch.Size = new System.Drawing.Size(116, 17); - this.IRCProviderSwitch.TabIndex = 12; - this.IRCProviderSwitch.TabStop = true; - this.IRCProviderSwitch.Text = "Internet Relay Chat"; - this.IRCProviderSwitch.UseVisualStyleBackColor = true; - this.IRCProviderSwitch.CheckedChanged += new System.EventHandler(this.IRCProviderSwitch_CheckedChanged); - // - // ChatProviderTitle - // - this.ChatProviderTitle.AutoSize = true; - this.ChatProviderTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ChatProviderTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChatProviderTitle.Location = new System.Drawing.Point(20, 15); - this.ChatProviderTitle.Name = "ChatProviderTitle"; - this.ChatProviderTitle.Size = new System.Drawing.Size(144, 18); - this.ChatProviderTitle.TabIndex = 13; - this.ChatProviderTitle.Text = "Editing Provider:"; - this.ChatProviderTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ChannelsTitle - // - this.ChannelsTitle.AutoSize = true; - this.ChannelsTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ChannelsTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChannelsTitle.Location = new System.Drawing.Point(565, 15); - this.ChannelsTitle.Name = "ChannelsTitle"; - this.ChannelsTitle.Size = new System.Drawing.Size(234, 18); - this.ChannelsTitle.TabIndex = 11; - this.ChannelsTitle.Text = "Listen/Broadcast Channels:"; - this.ChannelsTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ChatAdminsTitle - // - this.ChatAdminsTitle.AutoSize = true; - this.ChatAdminsTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ChatAdminsTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ChatAdminsTitle.Location = new System.Drawing.Point(366, 15); - this.ChatAdminsTitle.Name = "ChatAdminsTitle"; - this.ChatAdminsTitle.Size = new System.Drawing.Size(75, 18); - this.ChatAdminsTitle.TabIndex = 9; - this.ChatAdminsTitle.Text = "Admins:"; - this.ChatAdminsTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // panel1 - // - this.panel1.Controls.Add(this.AdminModeSpecial); - this.panel1.Controls.Add(this.AdminModeNormal); - this.panel1.Location = new System.Drawing.Point(174, 168); - this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(202, 25); - this.panel1.TabIndex = 15; - // - // AdminModeSpecial - // - this.AdminModeSpecial.AutoSize = true; - this.AdminModeSpecial.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.AdminModeSpecial.Location = new System.Drawing.Point(87, 8); - this.AdminModeSpecial.Name = "AdminModeSpecial"; - this.AdminModeSpecial.Size = new System.Drawing.Size(94, 17); - this.AdminModeSpecial.TabIndex = 13; - this.AdminModeSpecial.TabStop = true; - this.AdminModeSpecial.Text = "Channel Mode"; - this.AdminModeSpecial.UseVisualStyleBackColor = true; - // - // AdminModeNormal - // - this.AdminModeNormal.AutoSize = true; - this.AdminModeNormal.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.AdminModeNormal.Location = new System.Drawing.Point(3, 8); - this.AdminModeNormal.Name = "AdminModeNormal"; - this.AdminModeNormal.Size = new System.Drawing.Size(78, 17); - this.AdminModeNormal.TabIndex = 12; - this.AdminModeNormal.TabStop = true; - this.AdminModeNormal.Text = "Nicknames"; - this.AdminModeNormal.UseVisualStyleBackColor = true; - this.AdminModeNormal.CheckedChanged += new System.EventHandler(this.AdminModeNormal_CheckedChanged); - // - // ServerPanel - // - this.ServerPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.ServerPanel.Controls.Add(this.AutoUpdateMLabel); - this.ServerPanel.Controls.Add(this.AutoUpdateInterval); - this.ServerPanel.Controls.Add(this.AutoUpdateCheckbox); - this.ServerPanel.Controls.Add(this.ServerPageRefreshButton); - this.ServerPanel.Controls.Add(this.AutostartCheckbox); - this.ServerPanel.Controls.Add(this.WebclientCheckBox); - this.ServerPanel.Controls.Add(this.WorldAnnounceButton); - this.ServerPanel.Controls.Add(this.WorldAnnounceField); - this.ServerPanel.Controls.Add(this.projectNameText); - this.ServerPanel.Controls.Add(this.WorldAnnounceLabel); - this.ServerPanel.Controls.Add(this.SecuritySelector); - this.ServerPanel.Controls.Add(this.SecurityTitle); - this.ServerPanel.Controls.Add(this.RemoveAllTestMergesButton); - this.ServerPanel.Controls.Add(this.ServerPathLabel); - this.ServerPanel.Controls.Add(this.CompileCancelButton); - this.ServerPanel.Controls.Add(this.ProjectPathLabel); - this.ServerPanel.Controls.Add(this.PortLabel); - this.ServerPanel.Controls.Add(this.PortSelector); - this.ServerPanel.Controls.Add(this.TestMergeManagerButton); - this.ServerPanel.Controls.Add(this.UpdateServerButton); - this.ServerPanel.Controls.Add(this.ServerGRestartButton); - this.ServerPanel.Controls.Add(this.ServerGStopButton); - this.ServerPanel.Controls.Add(this.ServerRestartButton); - this.ServerPanel.Controls.Add(this.ServerStopButton); - this.ServerPanel.Controls.Add(this.ServerStartButton); - this.ServerPanel.Controls.Add(this.CompilerStatusLabel); - this.ServerPanel.Controls.Add(this.CompilerLabel); - this.ServerPanel.Controls.Add(this.compileButton); - this.ServerPanel.Controls.Add(this.initializeButton); - this.ServerPanel.Controls.Add(this.ServerStatusLabel); - this.ServerPanel.Controls.Add(this.ServerStatusTitle); - this.ServerPanel.Location = new System.Drawing.Point(4, 22); - this.ServerPanel.Name = "ServerPanel"; - this.ServerPanel.Padding = new System.Windows.Forms.Padding(3); - this.ServerPanel.Size = new System.Drawing.Size(868, 366); - this.ServerPanel.TabIndex = 2; - this.ServerPanel.Text = "Server"; - // - // AutoUpdateMLabel - // - this.AutoUpdateMLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.AutoUpdateMLabel.AutoSize = true; - this.AutoUpdateMLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.AutoUpdateMLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.AutoUpdateMLabel.Location = new System.Drawing.Point(433, 136); - this.AutoUpdateMLabel.Name = "AutoUpdateMLabel"; - this.AutoUpdateMLabel.Size = new System.Drawing.Size(21, 18); - this.AutoUpdateMLabel.TabIndex = 47; - this.AutoUpdateMLabel.Text = "M"; - this.AutoUpdateMLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.AutoUpdateMLabel.Visible = false; - // - // AutoUpdateInterval - // - this.AutoUpdateInterval.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.AutoUpdateInterval.Location = new System.Drawing.Point(365, 135); - this.AutoUpdateInterval.Maximum = new decimal(new int[] { - 276447231, - 23283, - 0, - 0}); - this.AutoUpdateInterval.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.AutoUpdateInterval.Name = "AutoUpdateInterval"; - this.AutoUpdateInterval.Size = new System.Drawing.Size(62, 20); - this.AutoUpdateInterval.TabIndex = 46; - this.AutoUpdateInterval.Value = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.AutoUpdateInterval.Visible = false; - this.AutoUpdateInterval.ValueChanged += new System.EventHandler(this.AutoUpdateInterval_ValueChanged); - // - // AutoUpdateCheckbox - // - this.AutoUpdateCheckbox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.AutoUpdateCheckbox.AutoSize = true; - this.AutoUpdateCheckbox.Font = new System.Drawing.Font("Verdana", 12F); - this.AutoUpdateCheckbox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.AutoUpdateCheckbox.Location = new System.Drawing.Point(228, 134); - this.AutoUpdateCheckbox.Name = "AutoUpdateCheckbox"; - this.AutoUpdateCheckbox.Size = new System.Drawing.Size(131, 22); - this.AutoUpdateCheckbox.TabIndex = 45; - this.AutoUpdateCheckbox.Text = "Auto-Update"; - this.AutoUpdateCheckbox.UseVisualStyleBackColor = true; - this.AutoUpdateCheckbox.Visible = false; - this.AutoUpdateCheckbox.CheckedChanged += new System.EventHandler(this.AutoUpdateCheckbox_CheckedChanged); - // - // ServerPageRefreshButton - // - this.ServerPageRefreshButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.ServerPageRefreshButton.Location = new System.Drawing.Point(626, 13); - this.ServerPageRefreshButton.Name = "ServerPageRefreshButton"; - this.ServerPageRefreshButton.Size = new System.Drawing.Size(118, 28); - this.ServerPageRefreshButton.TabIndex = 43; - this.ServerPageRefreshButton.Text = "Refresh"; - this.ServerPageRefreshButton.UseVisualStyleBackColor = true; - this.ServerPageRefreshButton.Click += new System.EventHandler(this.ServerPageRefreshButton_Click); - // - // AutostartCheckbox - // - this.AutostartCheckbox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.AutostartCheckbox.AutoSize = true; - this.AutostartCheckbox.Font = new System.Drawing.Font("Verdana", 12F); - this.AutostartCheckbox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.AutostartCheckbox.Location = new System.Drawing.Point(754, 16); - this.AutostartCheckbox.Name = "AutostartCheckbox"; - this.AutostartCheckbox.Size = new System.Drawing.Size(104, 22); - this.AutostartCheckbox.TabIndex = 15; - this.AutostartCheckbox.Text = "Autostart"; - this.AutostartCheckbox.UseVisualStyleBackColor = true; - this.AutostartCheckbox.CheckedChanged += new System.EventHandler(this.AutostartCheckbox_CheckedChanged); - // - // WebclientCheckBox - // - this.WebclientCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.WebclientCheckBox.AutoSize = true; - this.WebclientCheckBox.Font = new System.Drawing.Font("Verdana", 12F); - this.WebclientCheckBox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.WebclientCheckBox.Location = new System.Drawing.Point(754, 57); - this.WebclientCheckBox.Name = "WebclientCheckBox"; - this.WebclientCheckBox.Size = new System.Drawing.Size(108, 22); - this.WebclientCheckBox.TabIndex = 42; - this.WebclientCheckBox.Text = "Webclient"; - this.WebclientCheckBox.UseVisualStyleBackColor = true; - this.WebclientCheckBox.CheckedChanged += new System.EventHandler(this.WebclientCheckBox_CheckedChanged); - // - // WorldAnnounceButton - // - this.WorldAnnounceButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.WorldAnnounceButton.Location = new System.Drawing.Point(785, 133); - this.WorldAnnounceButton.Name = "WorldAnnounceButton"; - this.WorldAnnounceButton.Size = new System.Drawing.Size(77, 20); - this.WorldAnnounceButton.TabIndex = 41; - this.WorldAnnounceButton.Text = "Send"; - this.WorldAnnounceButton.UseVisualStyleBackColor = true; - this.WorldAnnounceButton.Click += new System.EventHandler(this.WorldAnnounceButton_Click); - // - // WorldAnnounceField - // - this.WorldAnnounceField.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.WorldAnnounceField.Location = new System.Drawing.Point(566, 134); - this.WorldAnnounceField.Name = "WorldAnnounceField"; - this.WorldAnnounceField.Size = new System.Drawing.Size(213, 20); - this.WorldAnnounceField.TabIndex = 40; - // - // projectNameText - // - this.projectNameText.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.projectNameText.Location = new System.Drawing.Point(566, 163); - this.projectNameText.Name = "projectNameText"; - this.projectNameText.Size = new System.Drawing.Size(296, 20); - this.projectNameText.TabIndex = 29; - // - // WorldAnnounceLabel - // - this.WorldAnnounceLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.WorldAnnounceLabel.AutoSize = true; - this.WorldAnnounceLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.WorldAnnounceLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.WorldAnnounceLabel.Location = new System.Drawing.Point(466, 135); - this.WorldAnnounceLabel.Name = "WorldAnnounceLabel"; - this.WorldAnnounceLabel.Size = new System.Drawing.Size(94, 18); - this.WorldAnnounceLabel.TabIndex = 39; - this.WorldAnnounceLabel.Text = "Announce:"; - this.WorldAnnounceLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // SecuritySelector - // - this.SecuritySelector.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.SecuritySelector.FormattingEnabled = true; - this.SecuritySelector.Items.AddRange(new object[] { - "Trusted", - "Safe", - "Ultrasafe"}); - this.SecuritySelector.Location = new System.Drawing.Point(101, 134); - this.SecuritySelector.Name = "SecuritySelector"; - this.SecuritySelector.Size = new System.Drawing.Size(121, 21); - this.SecuritySelector.TabIndex = 38; - this.SecuritySelector.SelectedIndexChanged += new System.EventHandler(this.SecuritySelector_SelectedIndexChanged); - // - // SecurityTitle - // - this.SecurityTitle.AutoSize = true; - this.SecurityTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.SecurityTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.SecurityTitle.Location = new System.Drawing.Point(15, 139); - this.SecurityTitle.Name = "SecurityTitle"; - this.SecurityTitle.Size = new System.Drawing.Size(80, 18); - this.SecurityTitle.TabIndex = 37; - this.SecurityTitle.Text = "Security:"; - this.SecurityTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // RemoveAllTestMergesButton - // - this.RemoveAllTestMergesButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.RemoveAllTestMergesButton.Location = new System.Drawing.Point(489, 94); - this.RemoveAllTestMergesButton.Name = "RemoveAllTestMergesButton"; - this.RemoveAllTestMergesButton.Size = new System.Drawing.Size(142, 28); - this.RemoveAllTestMergesButton.TabIndex = 36; - this.RemoveAllTestMergesButton.Text = "Remove All Test Merges"; - this.RemoveAllTestMergesButton.UseVisualStyleBackColor = true; - this.RemoveAllTestMergesButton.Click += new System.EventHandler(this.RemoveAllTestMergesButton_Click); - // - // ServerPathLabel - // - this.ServerPathLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.ServerPathLabel.AutoSize = true; - this.ServerPathLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ServerPathLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ServerPathLabel.Location = new System.Drawing.Point(15, 165); - this.ServerPathLabel.Name = "ServerPathLabel"; - this.ServerPathLabel.Size = new System.Drawing.Size(109, 18); - this.ServerPathLabel.TabIndex = 33; - this.ServerPathLabel.Text = "Server Path:"; - this.ServerPathLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // CompileCancelButton - // - this.CompileCancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.CompileCancelButton.Enabled = false; - this.CompileCancelButton.Location = new System.Drawing.Point(382, 302); - this.CompileCancelButton.Name = "CompileCancelButton"; - this.CompileCancelButton.Size = new System.Drawing.Size(69, 31); - this.CompileCancelButton.TabIndex = 31; - this.CompileCancelButton.Text = "Cancel"; - this.CompileCancelButton.UseVisualStyleBackColor = true; - this.CompileCancelButton.Click += new System.EventHandler(this.CompileCancelButton_Click); - // - // ProjectPathLabel - // - this.ProjectPathLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.ProjectPathLabel.AutoSize = true; - this.ProjectPathLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ProjectPathLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ProjectPathLabel.Location = new System.Drawing.Point(445, 165); - this.ProjectPathLabel.Name = "ProjectPathLabel"; - this.ProjectPathLabel.Size = new System.Drawing.Size(115, 18); - this.ProjectPathLabel.TabIndex = 30; - this.ProjectPathLabel.Text = "Project Path:"; - this.ProjectPathLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // PortLabel - // - this.PortLabel.AutoSize = true; - this.PortLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.PortLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.PortLabel.Location = new System.Drawing.Point(4, 60); - this.PortLabel.Name = "PortLabel"; - this.PortLabel.Size = new System.Drawing.Size(48, 18); - this.PortLabel.TabIndex = 28; - this.PortLabel.Text = "Port:"; - this.PortLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // PortSelector - // - this.PortSelector.Location = new System.Drawing.Point(59, 60); - this.PortSelector.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.PortSelector.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.PortSelector.Name = "PortSelector"; - this.PortSelector.Size = new System.Drawing.Size(60, 20); - this.PortSelector.TabIndex = 27; - this.PortSelector.Value = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.PortSelector.ValueChanged += new System.EventHandler(this.PortSelector_ValueChanged); - // - // TestMergeManagerButton - // - this.TestMergeManagerButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.TestMergeManagerButton.Location = new System.Drawing.Point(343, 94); - this.TestMergeManagerButton.Name = "TestMergeManagerButton"; - this.TestMergeManagerButton.Size = new System.Drawing.Size(142, 28); - this.TestMergeManagerButton.TabIndex = 24; - this.TestMergeManagerButton.Text = "Test Merge Manager"; - this.TestMergeManagerButton.UseVisualStyleBackColor = true; - this.TestMergeManagerButton.Click += new System.EventHandler(this.TestMergeManagerButton_Click); - // - // UpdateServerButton - // - this.UpdateServerButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.UpdateServerButton.Location = new System.Drawing.Point(180, 94); - this.UpdateServerButton.Name = "UpdateServerButton"; - this.UpdateServerButton.Size = new System.Drawing.Size(157, 28); - this.UpdateServerButton.TabIndex = 21; - this.UpdateServerButton.Text = "Update Server"; - this.UpdateServerButton.UseVisualStyleBackColor = true; - this.UpdateServerButton.Click += new System.EventHandler(this.UpdateServerButton_Click); - // - // ServerGRestartButton - // - this.ServerGRestartButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.ServerGRestartButton.Location = new System.Drawing.Point(626, 54); - this.ServerGRestartButton.Name = "ServerGRestartButton"; - this.ServerGRestartButton.Size = new System.Drawing.Size(118, 28); - this.ServerGRestartButton.TabIndex = 20; - this.ServerGRestartButton.Text = "Graceful Restart"; - this.ServerGRestartButton.UseVisualStyleBackColor = true; - this.ServerGRestartButton.Click += new System.EventHandler(this.ServerGRestartButton_Click); - // - // ServerGStopButton - // - this.ServerGStopButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.ServerGStopButton.Appearance = System.Windows.Forms.Appearance.Button; - this.ServerGStopButton.Location = new System.Drawing.Point(502, 54); - this.ServerGStopButton.Name = "ServerGStopButton"; - this.ServerGStopButton.Size = new System.Drawing.Size(118, 28); - this.ServerGStopButton.TabIndex = 19; - this.ServerGStopButton.Text = "Graceful Stop"; - this.ServerGStopButton.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.ServerGStopButton.UseVisualStyleBackColor = true; - this.ServerGStopButton.CheckedChanged += new System.EventHandler(this.ServerGStopButton_Checked); - // - // ServerRestartButton - // - this.ServerRestartButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.ServerRestartButton.Location = new System.Drawing.Point(378, 54); - this.ServerRestartButton.Name = "ServerRestartButton"; - this.ServerRestartButton.Size = new System.Drawing.Size(118, 28); - this.ServerRestartButton.TabIndex = 18; - this.ServerRestartButton.Text = "Restart"; - this.ServerRestartButton.UseVisualStyleBackColor = true; - this.ServerRestartButton.Click += new System.EventHandler(this.ServerRestartButton_Click); - // - // ServerStopButton - // - this.ServerStopButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.ServerStopButton.Location = new System.Drawing.Point(254, 54); - this.ServerStopButton.Name = "ServerStopButton"; - this.ServerStopButton.Size = new System.Drawing.Size(118, 28); - this.ServerStopButton.TabIndex = 17; - this.ServerStopButton.Text = "Stop"; - this.ServerStopButton.UseVisualStyleBackColor = true; - this.ServerStopButton.Click += new System.EventHandler(this.ServerStopButton_Click); - // - // ServerStartButton - // - this.ServerStartButton.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.ServerStartButton.Location = new System.Drawing.Point(130, 54); - this.ServerStartButton.Name = "ServerStartButton"; - this.ServerStartButton.Size = new System.Drawing.Size(118, 28); - this.ServerStartButton.TabIndex = 16; - this.ServerStartButton.Text = "Start"; - this.ServerStartButton.UseVisualStyleBackColor = true; - this.ServerStartButton.Click += new System.EventHandler(this.ServerStartButton_Click); - // - // CompilerStatusLabel - // - this.CompilerStatusLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.CompilerStatusLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.CompilerStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.CompilerStatusLabel.Location = new System.Drawing.Point(110, 271); - this.CompilerStatusLabel.Name = "CompilerStatusLabel"; - this.CompilerStatusLabel.Size = new System.Drawing.Size(618, 28); - this.CompilerStatusLabel.TabIndex = 14; - this.CompilerStatusLabel.Text = "Idle"; - this.CompilerStatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // CompilerLabel - // - this.CompilerLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.CompilerLabel.AutoSize = true; - this.CompilerLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.CompilerLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.CompilerLabel.Location = new System.Drawing.Point(379, 203); - this.CompilerLabel.Name = "CompilerLabel"; - this.CompilerLabel.Size = new System.Drawing.Size(80, 18); - this.CompilerLabel.TabIndex = 13; - this.CompilerLabel.Text = "Compiler"; - this.CompilerLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // compileButton - // - this.compileButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; - this.compileButton.Enabled = false; - this.compileButton.Location = new System.Drawing.Point(456, 240); - this.compileButton.Name = "compileButton"; - this.compileButton.Size = new System.Drawing.Size(159, 28); - this.compileButton.TabIndex = 12; - this.compileButton.Text = "Copy from Repo and Compile"; - this.compileButton.UseVisualStyleBackColor = true; - this.compileButton.Click += new System.EventHandler(this.CompileButton_Click); - // - // initializeButton - // - this.initializeButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; - this.initializeButton.Enabled = false; - this.initializeButton.Location = new System.Drawing.Point(219, 240); - this.initializeButton.Name = "initializeButton"; - this.initializeButton.Size = new System.Drawing.Size(159, 28); - this.initializeButton.TabIndex = 11; - this.initializeButton.Text = "Initialize Game Folders"; - this.initializeButton.UseVisualStyleBackColor = true; - this.initializeButton.Click += new System.EventHandler(this.InitializeButton_Click); - // - // ServerStatusLabel - // - this.ServerStatusLabel.AutoSize = true; - this.ServerStatusLabel.Font = new System.Drawing.Font("Verdana", 10F); - this.ServerStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ServerStatusLabel.Location = new System.Drawing.Point(136, 19); - this.ServerStatusLabel.Name = "ServerStatusLabel"; - this.ServerStatusLabel.Size = new System.Drawing.Size(73, 17); - this.ServerStatusLabel.TabIndex = 9; - this.ServerStatusLabel.Text = "Unknown"; - this.ServerStatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // ServerStatusTitle - // - this.ServerStatusTitle.AutoSize = true; - this.ServerStatusTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ServerStatusTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ServerStatusTitle.Location = new System.Drawing.Point(15, 17); - this.ServerStatusTitle.Name = "ServerStatusTitle"; - this.ServerStatusTitle.Size = new System.Drawing.Size(125, 18); - this.ServerStatusTitle.TabIndex = 8; - this.ServerStatusTitle.Text = "Server Status:"; - this.ServerStatusTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // BYONDPanel - // - this.BYONDPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.BYONDPanel.Controls.Add(this.BYONDRefreshButton); - this.BYONDPanel.Controls.Add(this.LatestVersionLabel); - this.BYONDPanel.Controls.Add(this.LatestVersionTitle); - this.BYONDPanel.Controls.Add(this.StagedVersionLabel); - this.BYONDPanel.Controls.Add(this.StagedVersionTitle); - this.BYONDPanel.Controls.Add(this.StatusLabel); - this.BYONDPanel.Controls.Add(this.VersionLabel); - this.BYONDPanel.Controls.Add(this.VersionTitle); - this.BYONDPanel.Controls.Add(this.MinorVersionLabel); - this.BYONDPanel.Controls.Add(this.MajorVersionLabel); - this.BYONDPanel.Controls.Add(this.UpdateButton); - this.BYONDPanel.Controls.Add(this.MinorVersionNumeric); - this.BYONDPanel.Controls.Add(this.MajorVersionNumeric); - this.BYONDPanel.Location = new System.Drawing.Point(4, 22); - this.BYONDPanel.Name = "BYONDPanel"; - this.BYONDPanel.Size = new System.Drawing.Size(868, 366); - this.BYONDPanel.TabIndex = 1; - this.BYONDPanel.Text = "BYOND"; - // - // BYONDRefreshButton - // - this.BYONDRefreshButton.Location = new System.Drawing.Point(462, 252); - this.BYONDRefreshButton.Name = "BYONDRefreshButton"; - this.BYONDRefreshButton.Size = new System.Drawing.Size(118, 28); - this.BYONDRefreshButton.TabIndex = 14; - this.BYONDRefreshButton.Text = "Refresh"; - this.BYONDRefreshButton.UseVisualStyleBackColor = true; - this.BYONDRefreshButton.Click += new System.EventHandler(this.BYONDRefreshButton_Click); - // - // LatestVersionLabel - // - this.LatestVersionLabel.AutoSize = true; - this.LatestVersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.LatestVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.LatestVersionLabel.Location = new System.Drawing.Point(425, 93); - this.LatestVersionLabel.Name = "LatestVersionLabel"; - this.LatestVersionLabel.Size = new System.Drawing.Size(82, 18); - this.LatestVersionLabel.TabIndex = 13; - this.LatestVersionLabel.Text = "Unknown"; - this.LatestVersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // LatestVersionTitle - // - this.LatestVersionTitle.AutoSize = true; - this.LatestVersionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.LatestVersionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.LatestVersionTitle.Location = new System.Drawing.Point(286, 93); - this.LatestVersionTitle.Name = "LatestVersionTitle"; - this.LatestVersionTitle.Size = new System.Drawing.Size(133, 18); - this.LatestVersionTitle.TabIndex = 12; - this.LatestVersionTitle.Text = "Latest Version:"; - this.LatestVersionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // StagedVersionLabel - // - this.StagedVersionLabel.AutoSize = true; - this.StagedVersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.StagedVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.StagedVersionLabel.Location = new System.Drawing.Point(425, 131); - this.StagedVersionLabel.Name = "StagedVersionLabel"; - this.StagedVersionLabel.Size = new System.Drawing.Size(82, 18); - this.StagedVersionLabel.TabIndex = 11; - this.StagedVersionLabel.Text = "Unknown"; - this.StagedVersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.StagedVersionLabel.Visible = false; - // - // StagedVersionTitle - // - this.StagedVersionTitle.AutoSize = true; - this.StagedVersionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.StagedVersionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.StagedVersionTitle.Location = new System.Drawing.Point(282, 131); - this.StagedVersionTitle.Name = "StagedVersionTitle"; - this.StagedVersionTitle.Size = new System.Drawing.Size(137, 18); - this.StagedVersionTitle.TabIndex = 10; - this.StagedVersionTitle.Text = "Staged version:"; - this.StagedVersionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.StagedVersionTitle.Visible = false; - // - // StatusLabel - // - this.StatusLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.StatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.StatusLabel.Location = new System.Drawing.Point(303, 320); - this.StatusLabel.Name = "StatusLabel"; - this.StatusLabel.Size = new System.Drawing.Size(253, 37); - this.StatusLabel.TabIndex = 9; - this.StatusLabel.Text = "Idle"; - this.StatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // VersionLabel - // - this.VersionLabel.AutoSize = true; - this.VersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.VersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.VersionLabel.Location = new System.Drawing.Point(425, 56); - this.VersionLabel.Name = "VersionLabel"; - this.VersionLabel.Size = new System.Drawing.Size(82, 18); - this.VersionLabel.TabIndex = 8; - this.VersionLabel.Text = "Unknown"; - this.VersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // VersionTitle - // - this.VersionTitle.AutoSize = true; - this.VersionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.VersionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.VersionTitle.Location = new System.Drawing.Point(265, 56); - this.VersionTitle.Name = "VersionTitle"; - this.VersionTitle.Size = new System.Drawing.Size(154, 18); - this.VersionTitle.TabIndex = 7; - this.VersionTitle.Text = "Installed Version:"; - this.VersionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // MinorVersionLabel - // - this.MinorVersionLabel.AutoSize = true; - this.MinorVersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.MinorVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.MinorVersionLabel.Location = new System.Drawing.Point(521, 180); - this.MinorVersionLabel.Name = "MinorVersionLabel"; - this.MinorVersionLabel.Size = new System.Drawing.Size(59, 18); - this.MinorVersionLabel.TabIndex = 6; - this.MinorVersionLabel.Text = "Minor:"; - this.MinorVersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // MajorVersionLabel - // - this.MajorVersionLabel.AutoSize = true; - this.MajorVersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.MajorVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.MajorVersionLabel.Location = new System.Drawing.Point(276, 180); - this.MajorVersionLabel.Name = "MajorVersionLabel"; - this.MajorVersionLabel.Size = new System.Drawing.Size(60, 18); - this.MajorVersionLabel.TabIndex = 5; - this.MajorVersionLabel.Text = "Major:"; - this.MajorVersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // UpdateButton - // - this.UpdateButton.Location = new System.Drawing.Point(268, 252); - this.UpdateButton.Name = "UpdateButton"; - this.UpdateButton.Size = new System.Drawing.Size(118, 28); - this.UpdateButton.TabIndex = 3; - this.UpdateButton.Text = "Update"; - this.UpdateButton.UseVisualStyleBackColor = true; - this.UpdateButton.Click += new System.EventHandler(this.UpdateButton_Click); - // - // MinorVersionNumeric - // - this.MinorVersionNumeric.Location = new System.Drawing.Point(490, 210); - this.MinorVersionNumeric.Maximum = new decimal(new int[] { - 10000, - 0, - 0, - 0}); - this.MinorVersionNumeric.Name = "MinorVersionNumeric"; - this.MinorVersionNumeric.Size = new System.Drawing.Size(120, 20); - this.MinorVersionNumeric.TabIndex = 2; - this.MinorVersionNumeric.Value = new decimal(new int[] { - 1381, - 0, - 0, - 0}); - // - // MajorVersionNumeric - // - this.MajorVersionNumeric.Location = new System.Drawing.Point(245, 210); - this.MajorVersionNumeric.Maximum = new decimal(new int[] { - 1000, - 0, - 0, - 0}); - this.MajorVersionNumeric.Name = "MajorVersionNumeric"; - this.MajorVersionNumeric.Size = new System.Drawing.Size(120, 20); - this.MajorVersionNumeric.TabIndex = 1; - this.MajorVersionNumeric.Value = new decimal(new int[] { - 511, - 0, - 0, - 0}); - // - // RepoPanel - // - this.RepoPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.RepoPanel.Controls.Add(this.SyncCommitsCheckBox); - this.RepoPanel.Controls.Add(this.TGSJsonUpdate); - this.RepoPanel.Controls.Add(this.RepoRefreshButton); - this.RepoPanel.Controls.Add(this.BackupTagsList); - this.RepoPanel.Controls.Add(this.ResetRemote); - this.RepoPanel.Controls.Add(this.RecloneButton); - this.RepoPanel.Controls.Add(this.RepoBranchTextBox); - this.RepoPanel.Controls.Add(this.RepoRemoteTextBox); - this.RepoPanel.Controls.Add(this.RepoGenChangelogButton); - this.RepoPanel.Controls.Add(this.TestmergeSelector); - this.RepoPanel.Controls.Add(this.TestMergeListLabel); - this.RepoPanel.Controls.Add(this.CurrentRevisionLabel); - this.RepoPanel.Controls.Add(this.RepoApplyButton); - this.RepoPanel.Controls.Add(this.HardReset); - this.RepoPanel.Controls.Add(this.UpdateRepoButton); - this.RepoPanel.Controls.Add(this.MergePRButton); - this.RepoPanel.Controls.Add(this.IdentityLabel); - this.RepoPanel.Controls.Add(this.TestMergeListTitle); - this.RepoPanel.Controls.Add(this.RemoteNameTitle); - this.RepoPanel.Controls.Add(this.BranchNameTitle); - this.RepoPanel.Controls.Add(this.CurrentRevisionTitle); - this.RepoPanel.Controls.Add(this.CloneRepositoryButton); - this.RepoPanel.Controls.Add(this.RepoProgressBarLabel); - this.RepoPanel.Controls.Add(this.RepoProgressBar); - this.RepoPanel.Location = new System.Drawing.Point(4, 22); - this.RepoPanel.Name = "RepoPanel"; - this.RepoPanel.Padding = new System.Windows.Forms.Padding(3); - this.RepoPanel.Size = new System.Drawing.Size(868, 366); - this.RepoPanel.TabIndex = 0; - this.RepoPanel.Text = "Repository"; - // - // SyncCommitsCheckBox - // - this.SyncCommitsCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.SyncCommitsCheckBox.AutoSize = true; - this.SyncCommitsCheckBox.Font = new System.Drawing.Font("Verdana", 12F); - this.SyncCommitsCheckBox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.SyncCommitsCheckBox.Location = new System.Drawing.Point(574, 16); - this.SyncCommitsCheckBox.Name = "SyncCommitsCheckBox"; - this.SyncCommitsCheckBox.Size = new System.Drawing.Size(142, 22); - this.SyncCommitsCheckBox.TabIndex = 46; - this.SyncCommitsCheckBox.Text = "Sync Commits"; - this.SyncCommitsCheckBox.UseVisualStyleBackColor = true; - this.SyncCommitsCheckBox.Visible = false; - // - // TGSJsonUpdate - // - this.TGSJsonUpdate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.TGSJsonUpdate.Location = new System.Drawing.Point(722, 212); - this.TGSJsonUpdate.Name = "TGSJsonUpdate"; - this.TGSJsonUpdate.Size = new System.Drawing.Size(140, 29); - this.TGSJsonUpdate.TabIndex = 36; - this.TGSJsonUpdate.Text = "Update TGS3.json"; - this.TGSJsonUpdate.UseVisualStyleBackColor = true; - this.TGSJsonUpdate.Visible = false; - this.TGSJsonUpdate.Click += new System.EventHandler(this.TGSJsonUpdate_Click); - // - // RepoRefreshButton - // - this.RepoRefreshButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.RepoRefreshButton.Location = new System.Drawing.Point(722, 296); - this.RepoRefreshButton.Name = "RepoRefreshButton"; - this.RepoRefreshButton.Size = new System.Drawing.Size(140, 29); - this.RepoRefreshButton.TabIndex = 35; - this.RepoRefreshButton.Text = "Refresh"; - this.RepoRefreshButton.UseVisualStyleBackColor = true; - this.RepoRefreshButton.Visible = false; - this.RepoRefreshButton.Click += new System.EventHandler(this.RepoRefreshButton_Click); - // - // BackupTagsList - // - this.BackupTagsList.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.BackupTagsList.Items.AddRange(new object[] { - "None"}); - this.BackupTagsList.Location = new System.Drawing.Point(122, 142); - this.BackupTagsList.Name = "BackupTagsList"; - this.BackupTagsList.ScrollAlwaysVisible = true; - this.BackupTagsList.Size = new System.Drawing.Size(535, 95); - this.BackupTagsList.TabIndex = 34; - this.BackupTagsList.Visible = false; - // - // ResetRemote - // - this.ResetRemote.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.ResetRemote.Location = new System.Drawing.Point(722, 81); - this.ResetRemote.Name = "ResetRemote"; - this.ResetRemote.Size = new System.Drawing.Size(140, 29); - this.ResetRemote.TabIndex = 33; - this.ResetRemote.Text = "Reset To Remote"; - this.ResetRemote.UseVisualStyleBackColor = true; - this.ResetRemote.Visible = false; - this.ResetRemote.Click += new System.EventHandler(this.ResetRemote_Click); - // - // RecloneButton - // - this.RecloneButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.RecloneButton.Location = new System.Drawing.Point(722, 247); - this.RecloneButton.Name = "RecloneButton"; - this.RecloneButton.Size = new System.Drawing.Size(140, 29); - this.RecloneButton.TabIndex = 32; - this.RecloneButton.Text = "Reclone"; - this.RecloneButton.UseVisualStyleBackColor = true; - this.RecloneButton.Visible = false; - this.RecloneButton.Click += new System.EventHandler(this.RecloneButton_Click); - // - // RepoBranchTextBox - // - this.RepoBranchTextBox.Location = new System.Drawing.Point(122, 70); - this.RepoBranchTextBox.Name = "RepoBranchTextBox"; - this.RepoBranchTextBox.Size = new System.Drawing.Size(535, 20); - this.RepoBranchTextBox.TabIndex = 15; - this.RepoBranchTextBox.Visible = false; - // - // RepoRemoteTextBox - // - this.RepoRemoteTextBox.Location = new System.Drawing.Point(122, 44); - this.RepoRemoteTextBox.Name = "RepoRemoteTextBox"; - this.RepoRemoteTextBox.Size = new System.Drawing.Size(535, 20); - this.RepoRemoteTextBox.TabIndex = 14; - this.RepoRemoteTextBox.Visible = false; - // - // RepoGenChangelogButton - // - this.RepoGenChangelogButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.RepoGenChangelogButton.Location = new System.Drawing.Point(722, 177); - this.RepoGenChangelogButton.Name = "RepoGenChangelogButton"; - this.RepoGenChangelogButton.Size = new System.Drawing.Size(140, 29); - this.RepoGenChangelogButton.TabIndex = 27; - this.RepoGenChangelogButton.Text = "Generate Changelog"; - this.RepoGenChangelogButton.UseVisualStyleBackColor = true; - this.RepoGenChangelogButton.Visible = false; - this.RepoGenChangelogButton.Click += new System.EventHandler(this.RepoGenChangelogButton_Click); - // - // TestmergeSelector - // - this.TestmergeSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.TestmergeSelector.Location = new System.Drawing.Point(722, 151); - this.TestmergeSelector.Maximum = new decimal(new int[] { - 1000000, - 0, - 0, - 0}); - this.TestmergeSelector.Name = "TestmergeSelector"; - this.TestmergeSelector.Size = new System.Drawing.Size(140, 20); - this.TestmergeSelector.TabIndex = 22; - this.TestmergeSelector.Visible = false; - // - // TestMergeListLabel - // - this.TestMergeListLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.TestMergeListLabel.HorizontalScrollbar = true; - this.TestMergeListLabel.Items.AddRange(new object[] { - "None"}); - this.TestMergeListLabel.Location = new System.Drawing.Point(122, 260); - this.TestMergeListLabel.Name = "TestMergeListLabel"; - this.TestMergeListLabel.ScrollAlwaysVisible = true; - this.TestMergeListLabel.Size = new System.Drawing.Size(535, 95); - this.TestMergeListLabel.TabIndex = 21; - this.TestMergeListLabel.Visible = false; - // - // CurrentRevisionLabel - // - this.CurrentRevisionLabel.AutoSize = true; - this.CurrentRevisionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.CurrentRevisionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.CurrentRevisionLabel.Location = new System.Drawing.Point(162, 14); - this.CurrentRevisionLabel.Name = "CurrentRevisionLabel"; - this.CurrentRevisionLabel.Size = new System.Drawing.Size(82, 18); - this.CurrentRevisionLabel.TabIndex = 20; - this.CurrentRevisionLabel.Text = "Unknown"; - this.CurrentRevisionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.CurrentRevisionLabel.Visible = false; - // - // RepoApplyButton - // - this.RepoApplyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.RepoApplyButton.Location = new System.Drawing.Point(722, 331); - this.RepoApplyButton.Name = "RepoApplyButton"; - this.RepoApplyButton.Size = new System.Drawing.Size(140, 29); - this.RepoApplyButton.TabIndex = 17; - this.RepoApplyButton.Text = "Apply Changes"; - this.RepoApplyButton.UseVisualStyleBackColor = true; - this.RepoApplyButton.Visible = false; - this.RepoApplyButton.Click += new System.EventHandler(this.RepoApplyButton_Click); - // - // HardReset - // - this.HardReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.HardReset.Location = new System.Drawing.Point(722, 46); - this.HardReset.Name = "HardReset"; - this.HardReset.Size = new System.Drawing.Size(140, 29); - this.HardReset.TabIndex = 13; - this.HardReset.Text = "Reset To Origin Branch"; - this.HardReset.UseVisualStyleBackColor = true; - this.HardReset.Visible = false; - this.HardReset.Click += new System.EventHandler(this.HardReset_Click); - // - // UpdateRepoButton - // - this.UpdateRepoButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.UpdateRepoButton.Location = new System.Drawing.Point(722, 11); - this.UpdateRepoButton.Name = "UpdateRepoButton"; - this.UpdateRepoButton.Size = new System.Drawing.Size(140, 29); - this.UpdateRepoButton.TabIndex = 12; - this.UpdateRepoButton.Text = "Merge from Remote"; - this.UpdateRepoButton.UseVisualStyleBackColor = true; - this.UpdateRepoButton.Visible = false; - this.UpdateRepoButton.Click += new System.EventHandler(this.UpdateRepoButton_Click); - // - // MergePRButton - // - this.MergePRButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.MergePRButton.Location = new System.Drawing.Point(722, 116); - this.MergePRButton.Name = "MergePRButton"; - this.MergePRButton.Size = new System.Drawing.Size(140, 29); - this.MergePRButton.TabIndex = 11; - this.MergePRButton.Text = "Merge Pull Request"; - this.MergePRButton.UseVisualStyleBackColor = true; - this.MergePRButton.Visible = false; - this.MergePRButton.Click += new System.EventHandler(this.TestMergeButton_Click); - // - // IdentityLabel - // - this.IdentityLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.IdentityLabel.AutoSize = true; - this.IdentityLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.IdentityLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.IdentityLabel.Location = new System.Drawing.Point(6, 142); - this.IdentityLabel.Name = "IdentityLabel"; - this.IdentityLabel.Size = new System.Drawing.Size(116, 18); - this.IdentityLabel.TabIndex = 8; - this.IdentityLabel.Text = "Backup Tags:"; - this.IdentityLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.IdentityLabel.Visible = false; - // - // TestMergeListTitle - // - this.TestMergeListTitle.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.TestMergeListTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.TestMergeListTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.TestMergeListTitle.Location = new System.Drawing.Point(6, 260); - this.TestMergeListTitle.Name = "TestMergeListTitle"; - this.TestMergeListTitle.Size = new System.Drawing.Size(110, 41); - this.TestMergeListTitle.TabIndex = 6; - this.TestMergeListTitle.Text = "Active Test Merges:"; - this.TestMergeListTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.TestMergeListTitle.Visible = false; - // - // RemoteNameTitle - // - this.RemoteNameTitle.AutoSize = true; - this.RemoteNameTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.RemoteNameTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.RemoteNameTitle.Location = new System.Drawing.Point(6, 46); - this.RemoteNameTitle.Name = "RemoteNameTitle"; - this.RemoteNameTitle.Size = new System.Drawing.Size(78, 18); - this.RemoteNameTitle.TabIndex = 5; - this.RemoteNameTitle.Text = "Remote:"; - this.RemoteNameTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.RemoteNameTitle.Visible = false; - // - // BranchNameTitle - // - this.BranchNameTitle.AutoSize = true; - this.BranchNameTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.BranchNameTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.BranchNameTitle.Location = new System.Drawing.Point(6, 72); - this.BranchNameTitle.Name = "BranchNameTitle"; - this.BranchNameTitle.Size = new System.Drawing.Size(70, 18); - this.BranchNameTitle.TabIndex = 4; - this.BranchNameTitle.Text = "Branch:"; - this.BranchNameTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.BranchNameTitle.Visible = false; - // - // CurrentRevisionTitle - // - this.CurrentRevisionTitle.AutoSize = true; - this.CurrentRevisionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.CurrentRevisionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.CurrentRevisionTitle.Location = new System.Drawing.Point(6, 14); - this.CurrentRevisionTitle.Name = "CurrentRevisionTitle"; - this.CurrentRevisionTitle.Size = new System.Drawing.Size(150, 18); - this.CurrentRevisionTitle.TabIndex = 3; - this.CurrentRevisionTitle.Text = "Current Revision:"; - this.CurrentRevisionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.CurrentRevisionTitle.Visible = false; - // - // CloneRepositoryButton - // - this.CloneRepositoryButton.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.CloneRepositoryButton.Location = new System.Drawing.Point(311, 191); - this.CloneRepositoryButton.Name = "CloneRepositoryButton"; - this.CloneRepositoryButton.Size = new System.Drawing.Size(229, 34); - this.CloneRepositoryButton.TabIndex = 2; - this.CloneRepositoryButton.Text = "Clone Repository"; - this.CloneRepositoryButton.UseVisualStyleBackColor = true; - this.CloneRepositoryButton.Visible = false; - this.CloneRepositoryButton.Click += new System.EventHandler(this.CloneRepositoryButton_Click); - // - // RepoProgressBarLabel - // - this.RepoProgressBarLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.RepoProgressBarLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.RepoProgressBarLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.RepoProgressBarLabel.Location = new System.Drawing.Point(184, 142); - this.RepoProgressBarLabel.Name = "RepoProgressBarLabel"; - this.RepoProgressBarLabel.Size = new System.Drawing.Size(499, 46); - this.RepoProgressBarLabel.TabIndex = 1; - this.RepoProgressBarLabel.Text = "Searching for Repository..."; - this.RepoProgressBarLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // RepoProgressBar - // - this.RepoProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.RepoProgressBar.Location = new System.Drawing.Point(184, 191); - this.RepoProgressBar.Name = "RepoProgressBar"; - this.RepoProgressBar.Size = new System.Drawing.Size(499, 23); - this.RepoProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee; - this.RepoProgressBar.TabIndex = 0; - // - // Panels - // - this.Panels.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.Panels.Controls.Add(this.RepoPanel); - this.Panels.Controls.Add(this.BYONDPanel); - this.Panels.Controls.Add(this.ServerPanel); - this.Panels.Controls.Add(this.ChatPanel); - this.Panels.Controls.Add(this.StaticPanel); - this.Panels.Cursor = System.Windows.Forms.Cursors.Default; - this.Panels.Location = new System.Drawing.Point(12, 12); - this.Panels.Name = "Panels"; - this.Panels.SelectedIndex = 0; - this.Panels.Size = new System.Drawing.Size(876, 392); - this.Panels.TabIndex = 3; - // - // StaticPanel - // - this.StaticPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.StaticPanel.Controls.Add(this.RecreateStaticButton); - this.StaticPanel.Controls.Add(this.StaticFileDownloadButton); - this.StaticPanel.Controls.Add(this.StaticFilesRefreshButton); - this.StaticPanel.Controls.Add(this.StaticFileUploadButton); - this.StaticPanel.Controls.Add(this.StaticFileEditTextbox); - this.StaticPanel.Controls.Add(this.StaticFileDeleteButton); - this.StaticPanel.Controls.Add(this.StaticFileSaveButton); - this.StaticPanel.Controls.Add(this.StaticFileCreateButton); - this.StaticPanel.Controls.Add(this.label6); - this.StaticPanel.Controls.Add(this.StaticFileListBox); - this.StaticPanel.Location = new System.Drawing.Point(4, 22); - this.StaticPanel.Name = "StaticPanel"; - this.StaticPanel.Padding = new System.Windows.Forms.Padding(3); - this.StaticPanel.Size = new System.Drawing.Size(868, 366); - this.StaticPanel.TabIndex = 5; - this.StaticPanel.Text = "Static Files"; - // - // RecreateStaticButton - // - this.RecreateStaticButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.RecreateStaticButton.Location = new System.Drawing.Point(471, 6); - this.RecreateStaticButton.Name = "RecreateStaticButton"; - this.RecreateStaticButton.Size = new System.Drawing.Size(144, 22); - this.RecreateStaticButton.TabIndex = 33; - this.RecreateStaticButton.Text = "Recreate Static Directory"; - this.RecreateStaticButton.UseVisualStyleBackColor = true; - this.RecreateStaticButton.Click += new System.EventHandler(this.RecreateStaticButton_Click); - // - // StaticFileDownloadButton - // - this.StaticFileDownloadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.StaticFileDownloadButton.Location = new System.Drawing.Point(296, 6); - this.StaticFileDownloadButton.Name = "StaticFileDownloadButton"; - this.StaticFileDownloadButton.Size = new System.Drawing.Size(71, 22); - this.StaticFileDownloadButton.TabIndex = 32; - this.StaticFileDownloadButton.Text = "Download"; - this.StaticFileDownloadButton.UseVisualStyleBackColor = true; - this.StaticFileDownloadButton.Click += new System.EventHandler(this.StaticFileDownloadButton_Click); - // - // StaticFilesRefreshButton - // - this.StaticFilesRefreshButton.Location = new System.Drawing.Point(65, 6); - this.StaticFilesRefreshButton.Name = "StaticFilesRefreshButton"; - this.StaticFilesRefreshButton.Size = new System.Drawing.Size(71, 22); - this.StaticFilesRefreshButton.TabIndex = 31; - this.StaticFilesRefreshButton.Text = "Refresh"; - this.StaticFilesRefreshButton.UseVisualStyleBackColor = true; - this.StaticFilesRefreshButton.Click += new System.EventHandler(this.StaticFilesRefreshButton_Click); - // - // StaticFileUploadButton - // - this.StaticFileUploadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.StaticFileUploadButton.Location = new System.Drawing.Point(219, 6); - this.StaticFileUploadButton.Name = "StaticFileUploadButton"; - this.StaticFileUploadButton.Size = new System.Drawing.Size(71, 22); - this.StaticFileUploadButton.TabIndex = 30; - this.StaticFileUploadButton.Text = "Upload"; - this.StaticFileUploadButton.UseVisualStyleBackColor = true; - this.StaticFileUploadButton.Click += new System.EventHandler(this.StaticFileUploadButton_Click); - // - // StaticFileEditTextbox - // - this.StaticFileEditTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.StaticFileEditTextbox.Location = new System.Drawing.Point(219, 31); - this.StaticFileEditTextbox.Multiline = true; - this.StaticFileEditTextbox.Name = "StaticFileEditTextbox"; - this.StaticFileEditTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.StaticFileEditTextbox.Size = new System.Drawing.Size(643, 329); - this.StaticFileEditTextbox.TabIndex = 29; - // - // StaticFileDeleteButton - // - this.StaticFileDeleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.StaticFileDeleteButton.Location = new System.Drawing.Point(714, 6); - this.StaticFileDeleteButton.Name = "StaticFileDeleteButton"; - this.StaticFileDeleteButton.Size = new System.Drawing.Size(71, 22); - this.StaticFileDeleteButton.TabIndex = 28; - this.StaticFileDeleteButton.Text = "Delete"; - this.StaticFileDeleteButton.UseVisualStyleBackColor = true; - this.StaticFileDeleteButton.Click += new System.EventHandler(this.StaticFileDeleteButton_Click); - // - // StaticFileSaveButton - // - this.StaticFileSaveButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.StaticFileSaveButton.Location = new System.Drawing.Point(791, 6); - this.StaticFileSaveButton.Name = "StaticFileSaveButton"; - this.StaticFileSaveButton.Size = new System.Drawing.Size(71, 22); - this.StaticFileSaveButton.TabIndex = 27; - this.StaticFileSaveButton.Text = "Save"; - this.StaticFileSaveButton.UseVisualStyleBackColor = true; - this.StaticFileSaveButton.Click += new System.EventHandler(this.StaticFileSaveButton_Click); - // - // StaticFileCreateButton - // - this.StaticFileCreateButton.Location = new System.Drawing.Point(142, 6); - this.StaticFileCreateButton.Name = "StaticFileCreateButton"; - this.StaticFileCreateButton.Size = new System.Drawing.Size(71, 22); - this.StaticFileCreateButton.TabIndex = 26; - this.StaticFileCreateButton.Text = "Add"; - this.StaticFileCreateButton.UseVisualStyleBackColor = true; - this.StaticFileCreateButton.Click += new System.EventHandler(this.StaticFileCreateButton_Click); - // - // label6 - // - this.label6.AutoSize = true; - this.label6.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label6.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label6.Location = new System.Drawing.Point(6, 10); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(53, 18); - this.label6.TabIndex = 14; - this.label6.Text = "Files:"; - this.label6.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // StaticFileListBox - // - this.StaticFileListBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); - this.StaticFileListBox.FormattingEnabled = true; - this.StaticFileListBox.HorizontalScrollbar = true; - this.StaticFileListBox.Location = new System.Drawing.Point(6, 31); - this.StaticFileListBox.Name = "StaticFileListBox"; - this.StaticFileListBox.Size = new System.Drawing.Size(207, 329); - this.StaticFileListBox.TabIndex = 0; - this.StaticFileListBox.SelectedIndexChanged += new System.EventHandler(this.StaticFileListBox_SelectedIndexChanged); - // - // ControlPanel - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.ClientSize = new System.Drawing.Size(900, 415); - this.Controls.Add(this.Panels); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.Name = "ControlPanel"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "/tg/station 13 Server Control Panel"; - this.ChatPanel.ResumeLayout(false); - this.ChatPanel.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.ChatPortSelector)).EndInit(); - this.ChatProviderSelectorPanel.ResumeLayout(false); - this.ChatProviderSelectorPanel.PerformLayout(); - this.panel1.ResumeLayout(false); - this.panel1.PerformLayout(); - this.ServerPanel.ResumeLayout(false); - this.ServerPanel.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.AutoUpdateInterval)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.PortSelector)).EndInit(); - this.BYONDPanel.ResumeLayout(false); - this.BYONDPanel.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.MinorVersionNumeric)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.MajorVersionNumeric)).EndInit(); - this.RepoPanel.ResumeLayout(false); - this.RepoPanel.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.TestmergeSelector)).EndInit(); - this.Panels.ResumeLayout(false); - this.StaticPanel.ResumeLayout(false); - this.StaticPanel.PerformLayout(); - this.ResumeLayout(false); - - } - - #endregion - private System.ComponentModel.BackgroundWorker RepoBGW; - private System.ComponentModel.BackgroundWorker ServerStartBGW; - private System.Windows.Forms.TabPage ChatPanel; - private System.Windows.Forms.Label label5; - private System.Windows.Forms.Label label4; - private System.Windows.Forms.Label label3; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.ComboBox IRCModesComboBox; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.TextBox AdminChannelsTextbox; - private System.Windows.Forms.TextBox WDChannelsTextbox; - private System.Windows.Forms.TextBox GameChannelsTextbox; - private System.Windows.Forms.TextBox DevChannelsTextbox; - private System.Windows.Forms.TextBox ChatNicknameText; - private System.Windows.Forms.TextBox ChatServerText; - private System.Windows.Forms.TextBox AuthField2; - private System.Windows.Forms.TextBox AuthField1; - private System.Windows.Forms.TextBox ChatAdminsTextBox; - private System.Windows.Forms.Button ChatRefreshButton; - private System.Windows.Forms.Label ChatNicknameTitle; - private System.Windows.Forms.NumericUpDown ChatPortSelector; - private System.Windows.Forms.Label ChatPortTitle; - private System.Windows.Forms.Label ChatServerTitle; - private System.Windows.Forms.Button ChatApplyButton; - private System.Windows.Forms.Label AuthField2Title; - private System.Windows.Forms.Label AuthField1Title; - private System.Windows.Forms.Button ChatReconnectButton; - private System.Windows.Forms.Label ChatStatusLabel; - private System.Windows.Forms.Label ChatStatusTitle; - private System.Windows.Forms.CheckBox ChatEnabledCheckbox; - private System.Windows.Forms.Panel ChatProviderSelectorPanel; - private System.Windows.Forms.RadioButton DiscordProviderSwitch; - private System.Windows.Forms.RadioButton IRCProviderSwitch; - private System.Windows.Forms.Label ChatProviderTitle; - private System.Windows.Forms.Label ChannelsTitle; - private System.Windows.Forms.Label ChatAdminsTitle; - private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.RadioButton AdminModeSpecial; - private System.Windows.Forms.RadioButton AdminModeNormal; - private System.Windows.Forms.TabPage ServerPanel; - private System.Windows.Forms.CheckBox AutostartCheckbox; - private System.Windows.Forms.CheckBox WebclientCheckBox; - private System.Windows.Forms.Button WorldAnnounceButton; - private System.Windows.Forms.TextBox WorldAnnounceField; - private System.Windows.Forms.Label WorldAnnounceLabel; - private System.Windows.Forms.ComboBox SecuritySelector; - private System.Windows.Forms.Label SecurityTitle; - private System.Windows.Forms.Button RemoveAllTestMergesButton; - private System.Windows.Forms.Label ServerPathLabel; - private System.Windows.Forms.Button CompileCancelButton; - private System.Windows.Forms.Label ProjectPathLabel; - private System.Windows.Forms.Label PortLabel; - private System.Windows.Forms.NumericUpDown PortSelector; - private System.Windows.Forms.Button TestMergeManagerButton; - private System.Windows.Forms.Button UpdateServerButton; - private System.Windows.Forms.Button ServerGRestartButton; - private System.Windows.Forms.CheckBox ServerGStopButton; - private System.Windows.Forms.Button ServerRestartButton; - private System.Windows.Forms.Button ServerStopButton; - private System.Windows.Forms.Button ServerStartButton; - private System.Windows.Forms.Label CompilerStatusLabel; - private System.Windows.Forms.Label CompilerLabel; - private System.Windows.Forms.Button compileButton; - private System.Windows.Forms.Button initializeButton; - private System.Windows.Forms.Label ServerStatusLabel; - private System.Windows.Forms.Label ServerStatusTitle; - private System.Windows.Forms.TabPage BYONDPanel; - private System.Windows.Forms.Label LatestVersionLabel; - private System.Windows.Forms.Label LatestVersionTitle; - private System.Windows.Forms.Label StagedVersionLabel; - private System.Windows.Forms.Label StagedVersionTitle; - private System.Windows.Forms.Label StatusLabel; - private System.Windows.Forms.Label VersionLabel; - private System.Windows.Forms.Label VersionTitle; - private System.Windows.Forms.Label MinorVersionLabel; - private System.Windows.Forms.Label MajorVersionLabel; - private System.Windows.Forms.Button UpdateButton; - private System.Windows.Forms.NumericUpDown MinorVersionNumeric; - private System.Windows.Forms.NumericUpDown MajorVersionNumeric; - private System.Windows.Forms.TabPage RepoPanel; - private System.Windows.Forms.Button RepoRefreshButton; - private System.Windows.Forms.ListBox BackupTagsList; - private System.Windows.Forms.Button ResetRemote; - private System.Windows.Forms.Button RecloneButton; - private System.Windows.Forms.TextBox RepoBranchTextBox; - private System.Windows.Forms.TextBox RepoRemoteTextBox; - private System.Windows.Forms.Button RepoGenChangelogButton; - private System.Windows.Forms.NumericUpDown TestmergeSelector; - private System.Windows.Forms.ListBox TestMergeListLabel; - private System.Windows.Forms.Label CurrentRevisionLabel; - private System.Windows.Forms.Button RepoApplyButton; - private System.Windows.Forms.Button HardReset; - private System.Windows.Forms.Button UpdateRepoButton; - private System.Windows.Forms.Button MergePRButton; - private System.Windows.Forms.Label IdentityLabel; - private System.Windows.Forms.Label TestMergeListTitle; - private System.Windows.Forms.Label RemoteNameTitle; - private System.Windows.Forms.Label BranchNameTitle; - private System.Windows.Forms.Label CurrentRevisionTitle; - private System.Windows.Forms.Button CloneRepositoryButton; - private System.Windows.Forms.Label RepoProgressBarLabel; - private System.Windows.Forms.ProgressBar RepoProgressBar; - private System.Windows.Forms.TabControl Panels; - private System.Windows.Forms.TabPage StaticPanel; - private System.Windows.Forms.Button StaticFileCreateButton; - private System.Windows.Forms.Label label6; - private System.Windows.Forms.ListBox StaticFileListBox; - private System.Windows.Forms.Button StaticFileUploadButton; - private System.Windows.Forms.TextBox StaticFileEditTextbox; - private System.Windows.Forms.Button StaticFileDeleteButton; - private System.Windows.Forms.Button StaticFileSaveButton; - private System.Windows.Forms.Button StaticFilesRefreshButton; - private System.Windows.Forms.Button StaticFileDownloadButton; - private System.Windows.Forms.Button TGSJsonUpdate; - private System.Windows.Forms.Button BYONDRefreshButton; - private System.Windows.Forms.Button ServerPageRefreshButton; - private System.Windows.Forms.Button RecreateStaticButton; - private System.Windows.Forms.NumericUpDown AutoUpdateInterval; - private System.Windows.Forms.CheckBox AutoUpdateCheckbox; - private System.Windows.Forms.Label AutoUpdateMLabel; - private System.Windows.Forms.TextBox projectNameText; - private System.Windows.Forms.CheckBox SyncCommitsCheckBox; - } -} diff --git a/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.cs deleted file mode 100644 index 8f9368a096..0000000000 --- a/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Windows.Forms; -using TGS.Interface; - -namespace TGS.ControlPanel -{ - /// - /// The main form - /// - sealed partial class ControlPanel : CountedForm - { - /// - /// List of instances being used by open control panels - /// - public static IDictionary InstancesInUse { get; private set; } = new Dictionary(); - - /// - /// The for this - /// - readonly IInstance Instance; - - /// - /// Constructs a - /// - /// The to use - /// The to use - public ControlPanel(IServer server, IInstance instance) - { - InitializeComponent(); - - FormClosed += ControlPanel_FormClosed; - Panels.SelectedIndexChanged += Panels_SelectedIndexChanged; - Panels.SelectedIndex += Math.Min(Properties.Settings.Default.LastPageIndex, Panels.TabCount - 1); - - Instance = instance; - - InstancesInUse.Add(Instance.Metadata.Name, this); - - Text = String.Format("TGS {0} Instance: {1}", server.Version, Instance.Metadata.Name); - - InitRepoPage(); - InitBYONDPage(); - InitServerPage(); - - UpdateSelectedPanel(); - } - - /// - /// Called when the is closed - /// - /// The sender of the event - /// The - void ControlPanel_FormClosed(object sender, FormClosedEventArgs e) - { - InstancesInUse.Remove(Instance.Metadata.Name); - } - - /// - /// Called from - /// - void Cleanup() - { - InstancesInUse.Remove(Instance.Metadata.Name); - } - - private void Main_Resize(object sender, EventArgs e) - { - Panels.Location = new Point(10, 10); - Panels.Width = ClientSize.Width - 20; - Panels.Height = ClientSize.Height - 20; - } - - /// - /// Updates the content of of - /// - void UpdateSelectedPanel() - { - switch (Panels.SelectedIndex) - { - case 0: //repo - PopulateRepoFields(); - break; - case 1: //byond - UpdateBYONDButtons(); - break; - case 2: //scp - LoadServerPage(); - break; - case 3: //chat - LoadChatPage(); - break; - case 4: //static - InitStaticPage(); - break; - } - Properties.Settings.Default.LastPageIndex = Panels.SelectedIndex; - } - - void Panels_SelectedIndexChanged(object sender, EventArgs e) - { - UpdateSelectedPanel(); - } - - bool CheckAdminWithWarning() - { - if (Instance.Administration != null) - { - MessageBox.Show("Only system administrators may use this command!"); - return false; - } - return true; - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.resx b/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.resx deleted file mode 100644 index 74d58cbbd6..0000000000 --- a/legacy/V3/TGS.ControlPanel/ControlPanel/ControlPanel.resx +++ /dev/null @@ -1,2185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - - 993, 18 - - - - - AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAgIAAAAQAgAKgQAABGDgAAMDAAAAEA - IACoJQAA7h4AAKCgAAABACAAqJwBAJZEAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAA - AAAAAAAAAAAAAAAAAABeTTgAWkgyA1pIMilOOyMoRjMaRz4qEMM+KhDESDUcSU07IyhaSDIpWUgxA15N - NwAAAAAAAAAAAAAAAADSzsgAPyoQAEk2HTk9KQ7ZOSUK5DciB+43Igf/OCQJ/zkkCe46JQrkPSgO2UYz - Gjk8JwwA0s3HAAAAAABVQywAbV1JAk47IytALBK6XEo0/2xcSP9lVUD/mo+C/5CEdf9jUz7/ZFQ//2BP - Ov9LOCC6SzgfK2hYRAJSPygA////AUg1HHA7JgzkOSQJ/29gTP9wYU7/6+nm/+rn5f9rXEj/U0Ap/1NB - Kv9YRzD/Z1ZC/zsmC+RINBtw////AYFzYgU/KhCxOiUK/4V4aP+hl4r/ZFQ///b19P/a1tH/c2RR/87J - w//X083/Y1M+/3BhTv8+Kg//PyoQsYFzYgVYRi8cPSkOz0MvFf/a1tH/vreu/2RTPv/08/H//Pz7//Py - 8P/+/f3/+vn5/3JjUP+Hemr/RjMa/z0pDs9XRS8cQi4VnDciB/xsXEn/9PPx/7y1rP9kUz7/9PLx//// - //////////////f29f9qW0f/oJaJ/3BhTf83Igf8Qi4VnD8qEN45JAn/saie//////+7tKv/ZFM+//Ty - 8f//////7u3q/5SJev95a1n/Y1M9/9rX0v+zq6D/OSQJ/z8qEN5EMBeFNiEF/oV4aP//////vLSr/2RT - Pv/08vH//////+7t6v+UiXr/eWtZ/2NTPv/X0s3/h3pq/zYhBf5EMBeFRzMaZTYhBfuAc2L//////7y0 - q/9kUz7/9PLx//////////////////f29f9qWkX/rqaa/4R3Zv82IQX7RzMaZUUxGIc3Igf/X085/97b - 1/+9tqz/ZFM+//Tz8f/8/Pv/8/Lw//79/f/6+fn/cWJP/5KHeP9kUz7/NyIH/0UxGIdMOSE0PikPvzkk - Cf+Lf2//in5u/2JSPf/29fT/2tbR/3JkUf/PycP/19PN/2NTPv9oWEP/OycM/z0pD79LOCA0RDEYAE89 - JSU/KxHWcGBN/1NAKf9wYU3/9vX0/+rn5P9pWkX/VEIq/1NBKv9YRzD/ZlZB/zwnDdZPPSUlRTEYAGdY - QwAAAAAAQCwSkkw5If9cSjT/bF1J/6ujl/+lm4//j4N0/4R3Z/9lVUD/YE86/0IuFP8/KxGSAAAAAGdY - QwAAAAAAQy8WAFA+JiFDLxZmPCcMnzciBvM5JAn/NyIH/zgjCP87Jgz/OCMI8zwnDZ9DLxVmTzwlIUEs - EwAAAAAAAAAAAAAAAAAAAAAAQi4VAEYyGQc+KQ+GPSgO6EEtE61CLhStPSkP6D0pDoZEMBcHQS0TAAAA - AAAAAAAAAAAAAOAHAADgBwAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB - AADAAwAAwAMAAPAPAAAoAAAAGAAAADAAAAABACAAAAAAAAAJAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wD///8A////AP///wBVQy0AemxaA047I0NCLhTGQS0TxlE+J0aFd2cEYlI9AP// - /wD///8A////AP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF5NNwBhUDsMTToifk47 - I50+KQ+BQy8WmzwnDeg4Iwj/OCMI/z0oDupDLxacPikPgU47I51NOiF+YFA6DF1NNwAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAj4R1AB8HAABLOCBgOiUK+DYhBf82IQX/OCMI/zciB/82IQX/NiEF/zUg - Bf82IQX/NiEF/zYhBf86JQr4SzgfYB8HAACOgnMAAAAAAAAAAAAAAAAAAAAAAAAAAACMgXIACAAAAGBP - OS0/KxHVRzMa/2BPOf9eTTf/QSwT/0UyGf+Lf2//joJy/15NN/9fTjn/X085/11MNv9KNx//PysR1V9O - OC0GAAAAjIFyAAAAAAAAAAAAnpWIANrY0wFaSTIhRTIZdUAsEtc2IQb/d2lW/35xX/+Lf2//vLWs/8/K - xP/7+vr/wLqx/3NkUf9qWkb/alpG/25fS/+GeWj/ZFM+/z8qENdFMRh1WEYvIdTOyQGZjX8AbF1KAG1e - ShxFMhjMOSQJ/jciB/8zHgL/dmhV/1E/KP9oWET/+Pf2///////h3tr/UD4m/zsnDP9EMBb/QzAW/zsm - C/9BLRT/hnlp/0EtFP84Iwj+RTEYzG1dShxsXUkATDkgAE06Iig7JgzoNyIH/z4pD/9wYU3/ubKo/1RB - Kv9qWkb/+Pf3///////Hwbn/OyYL/4p+bv/c2NT/3NjT/6iek/87Jwz/e21b/1E+J/82IQb/OyYM6E06 - IihMOSAAWUcwAF1MNjs9KQ/0NSAE/2pbRv/z8vD/7evo/1JAKf9qWkb/+Pf3///////p5+T/tq6l/9vX - 0v///////////9XRy/9BLRP/in1t/25fS/81HwT/PSkP9F5NNztZRzEAAAAAAEo3H3A6JQr9NB8D/3xu - XP//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XRy/9BLBP/kIR1/35w - Xv80HwP/OiUK/Uo3HnAAAAAAUkApRj4pD+M3Igf/OiUK/6iekv//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/k4d5/6qhlf86JQr/NyIH/z4pD+NSPyhGQy8V1jgj - CP82IQb/emxa//Tz8f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yj - mP83Igf/mI6A//b29P97bVv/NiEG/zgjCP9DLxXWRTEYxDgjCP82IQb/m5CD////////////7Oro/1JA - Kf9qWkb/+Pf3/////////////////6ackP9XRS7/WEYw/0IuFf91Z1T/4d7Z//////+bkIP/NiEG/zgj - CP9FMRjEUT4nXTsmDPU2IAX/XUw2/+3r6f//////7Oro/1JAKf9qWkb/+Pf3/////////////////6ac - kP9XRS7/WEYv/0IuFP92Z1T/4t/b/+7s6v9dTDb/NiAF/zsmDPVRPiddYlE7Gz8qENQ3Igb/SDUc/93a - 1f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yjmP83Igf/mo+B/9/c - 2P9INRz/NyIG/z8qENRhUTwbX044Jj8qEOA2IAX/WUcx/+3r6f//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/kod4/+/t6/9ZRzD/NiAF/z8qEOBfTjgmVEIqRjwn - DfI3IQb/TDkg/8bAuf//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XR - y/9BLBP/kYV2/8nDvP9MOSH/NiEG/zwnDfJUQitGVUMsQj4pD+Q4Iwj/NiEG/047I//PysT/7uzq/1JA - Kf9qWkb/+Pf3///////p5+T/tq6l/9vX0v///////////9XRy/9CLhT/fG5c/1tKNP82IQX/OCMI/z4p - D+RVQyxCwry1Ak07I0Y/KhDUNyIH/0s4IP+akIL/jIBw/0UxGP9rW0f/+Pf3///////Fv7j/OiYL/4p+ - bv/c2NT/3NjT/6iek/87Jwz/eWpY/006Iv83Igf/PioQ1E06Ika8ta0CY1M+AEIvFQBYRjA1PCcN3lA9 - Jf96a1n/NyIH/zQeA/9qW0b/+Pf2///////e29b/TToi/zsmC/9DLxb/QzAW/zsmC/9BLRT/hnlp/0Et - FP89KA7eV0YvNUMwFgBhUDwAAAAAAG5fSwD5+PcBQCwSm0QwF/+FeGj/gnVk/3lrWf+Vinz/9fPy//// - ///39vX/ubGo/4F0Y/9yYk//alpG/25fS/+GeWj/Y1M9/zciB/9BLhSb+fr5AW5gTAAAAAAAAAAAAAAA - AABBLRMASjcfVjsmDPs7Jgv/Qy8W/047I/9aSDL/hHdm/6CWif9xYk7/cWJP/52Thv+DdWX/Xk04/11M - Nv9KNx//OCMH/zsmDPtKNx9WQS0TAAAAAAAAAAAAAAAAAAAAAABcSzUAY1I9E006ImxFMRieQCsSxzgj - CPU2IAX/NiEG/zYhBv81IAT/NSAE/zYhBv82IQb/NSAF/zciB/U+KhDHQy8Wnko3H2xiUTwTXEo0AAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGXiwDJxL0BalpGDT4qEHM5JQrzOCMI/zgjCP86Jgv7OyYL+zgj - CP84Iwj/OSQK8z4pD3NnV0MNt66lAZKGeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzggAFNBKgVBLRSBPysR60QwFrBNOyNpTzwlaUYyGbBALBLrQS0TgVA+JgVJNh0AAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA/4H/APgAHwD4AB8A8AAPAIAAAQCAAAEAgAABAIAAAQCAAAEAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAADAMAAAwDgAAcA4AAHAPgAHwD+AH8AKAAAACAA - AABAAAAAAQAgAAAAAAAAEAAAEwsAABMLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHlrWQCGeWkEVkQtRkUyGclEMBfJU0EpR52UhwZ7blwAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAalpHAAAAAABlVUAmcGBNQG5fTDE8Jw0dXEs1L0g1HIw9KQ7qOCMI/zgjCP8+KQ/sTToikltK - My88Jw0dbl9MMXBgTUBlVUAmAAAAAGdXQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABaSDIAY1I9IEQwF8pBLRP5PysR7TkkCeA+KQ/rOSUK/jgjCP84Iwj/OCMI/zgj - CP85JAr+PSkP6zkkCeA/KxHtQS0T+UQwF8piUTwgWUgxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAeGpXALy2rQJKNx+MOSQK/zchBv83Igb/NyIH/zgiB/84Iwj/NyIH/zUg - Bf81IAX/NyIH/zciB/83Igf/NyIH/zciBv83Igf/OSQK/0o3Hoy6s6oCdmhWAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAANnUzwA0HwMAXk44Rz4pD+05JQr/RTEY/0g1HP9GMhn/OSQJ/zUf - BP9AKxH/e21b/3xuXP9HMxr/RjIZ/0g0HP9INBv/RzQb/0EtFP83Igf/PSgO7V5NN0czHgIA2dTPAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ2ThgAWAAAAYlE8ElRCK2BFMRfVNiEG/1xLNf+pn5T/lIl7/6ee - kv+DdmX/bl9L/763rv/5+Pf/8vHv/66mm/+Wi33/lop8/5aKfP+Wi33/mo+C/4l9bf9HNBv/Qy8W1VRC - K2BhUDsSJxAAAI+CcwAAAAAAAAAAAAAAAAByY1AAg3ZlFVE/J2lALBLFOyYM+jgjCP81IAT/a1tH/4Z5 - af83Igf/e21b//X08//39vX///////v6+f+aj4L/STYd/z4pD/89KA7/PSgO/z0pDv9DLxb/dGZT/5WK - e/8+KQ//OyYL+j8rEcVQPSVpgHJhFW5eSwAAAAAAAAAAAEw6IgBgTzpiPioQ/jgjCP84Iwj/OCMI/zMe - Av9pWUT/g3Zl/zEcAP93aVb//Pv7////////////1tLN/0UyGf83Igf/Qi4U/0MvFv9DLxb/QCwS/zci - B/83IQb/kIR1/15NN/82IAX/OCMI/z4qEP5gTzliTDkhAAAAAAAAAAAAPCgNAEMvFWk5JAn/OCMI/zgj - CP86JQr/V0Uv/66lmv+Ie2v/MRwA/3dpVv/8+/v///////////+5sqn/NSAF/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cWJP/zUgBP84Iwj/OSQJ/0MvFmk8KA0AAAAAAAAAAAAAAAAAU0AphDkk - Cf84Iwj/OCIH/4x/cP/q5+T//////4p+bv8xHAD/d2lW//z7+////////////8fBuv9oWUT/fW9d/+7s - 6v////////////////+Wi3z/Mh0B/3VmU/+YjX//NyIH/zgjCP85JAn/U0EphAAAAAAAAAAAemlYAAAA - AABPPSWYOSQJ/zgjCP87Jgv/vbat////////////in1u/zEcAP93aVb//Pv7/////////////Pz8//j3 - 9v/5+Pj//v7+/////////////////5iMf/8yHQH/d2hW/7ixp/87Jgv/OCMI/zkkCf9PPSWYAAAAAHRm - UgBcSzUAZVQ/J0EtE9M4Iwj/OCMI/z0pDv/JxL3///////////+KfW7/MRwA/3dpVv/8+/v///////// - ////////////////////////////////////////mIx+/zIdAf93aFb/xb+3/z0pD/84Iwj/OCMI/0Et - E9NjUz4nW0kzAHlrWBBJNh2oOSQJ/zgjCP82IQb/U0Ap/+He2v///////////4p9bv8xHAD/d2lW//z7 - +/////////////////////////////////////////////////+YjH7/Mh0B/3VnVP/d2dX/U0Ep/zYh - Bv84Iwj/OSQJ/0g1HKh3aVcQTjsjjDsmC/k4Iwj/NyIH/0AsEv+zq6H/////////////////in1u/zEc - AP93aVb//Pv7/////////////////////////////////////////////////4t/b/8yHAD/c2RR//v7 - +v+0raP/QCwT/zciB/84Iwj/OyYL+U47I4xFMRjoOCMH/zgjCP82IAX/hHdm//r6+f////////////// - //+KfW7/MRwA/3dpVv/8+/v///////////////////////f29f/Szsj/zcjC/87Jw/+wqJ7/Tzwk/zUf - BP+MgHD///7+//v6+v+Ed2f/NiAF/zgjCP84Iwf/RTEY6E48JJs5JAn/OCMI/zUgBf97bVv/+vn5//// - /////////////4p9bv8xHAD/d2lW//z7+///////////////////////4d7Z/1A+Jv89KQ//PioQ/zkk - Cv89KQ//nJGE/+zq5///////+vn5/3ttW/81IAX/OCMI/zkkCf9OPCSbYlI9O0AsEuk4Iwf/NyIH/0Yy - Gf/Tzsj/////////////////in1u/zEcAP93aVb//Pv7///////////////////////h3tn/UD4m/z0p - D/8+KhD/OSQJ/z0pD/+ckYT/7Oro///////Tzsj/RjIZ/zciB/84Iwf/QCwS6WJSPTuooJQFRTEYoDgj - CP84Iwj/NiEG/6KYi/////////////////+KfW7/MRwA/3dpVv/8+/v///////////////////////f2 - 9f/Szsj/zcjC/87Jw/+wqJ3/Tzwk/zUfBP+MgHD//////6KYi/82IQX/OCMI/zgjCP9FMRigqJ+TBQAA - AABOOyOLOSQJ/zgjCP84Iwj/sKid/////////////////4p9bv8xHAD/d2lW//z7+/////////////// - //////////////////////////////////+Lf2//MhwA/3NkUf/+/v7/sKid/zgjCP84Iwj/OSQJ/048 - I4sAAAAAycO7BUk1Ha04Iwj/OCIH/0ArEf/MxsD/////////////////in1u/zEcAP93aVb//Pv7//// - /////////////////////////////////////////////5iMf/8yHQH/dGVS//79/f/Mx8D/QCsR/zgi - B/84Iwj/STYdrcjEvAV8b10UQzAWzDgjCP84Iwf/PikP/62kmf/9/Pz///////////+KfW7/MRwA/3dp - Vv/8+/v/////////////////////////////////////////////////mIx+/zIdAf90ZVL/+fj3/66m - m/8+KQ//OCMH/zgjCP9EMBbMfW9eFGVVQCZBLBPhNyIH/zgjCP83Igf/Qy8V/5yShf/18/L//////4p9 - bv8xHAD/d2lW//z7+/////////////z8/P/49/b/+fj4//7+/v////////////////+YjH//Mh0B/3Rl - Uv+nnZH/Qy8W/zciB/84Iwj/NyIH/0EsE+FlVUAmhHZmDE48JIE8Jw3zOCMI/zgjCP81IAX/VkQt/9bS - zf/+/v7/in1t/zEcAP93aVb//Pv7////////////x8G6/2hZRP99b13/7uzq/////////////////5aL - fP8yHQH/cWJO/3RlUv80HwP/OCMI/zgjCP88Jw3zTTsjgYF0YwxjUj0AfG5dBE88JV4/KxHkOCMI/zUg - BP91Z1T/kIV2/3NlUv9XRS//Mx4C/3dpVv/8+/v///////////+3sKb/NSAE/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cmNP/zUgBP84Iwj/PyoQ5E48JF58blwEY1I9AAAAAAByZVEA////AF5N - OEk+KhDmNR8E/3ZnVP9tXUn/LxkA/zUfBP80HgP/d2hW//z7+////////////9LNx/9CLhX/OCMI/0Iu - FP9DLxb/Qy8W/0AsEv83Igf/NyEG/5CEdf9eTTf/NiAF/z4qEOZdTTdJ////AHBhTgAAAAAAAAAAAAAA - AACBc2MAvreuA0UxGKM1IAX/cWJP/5qQgv9pWUX/Wkgy/0g1HP9/cWD//Pv7////////////+fj4/5SJ - ev9GMhn/OyYM/zwnDf89KA7/PSkO/0MvFv90ZlP/lYp8/z4qD/84Iwj/RTEYo764sAOBdGQAAAAAAAAA - AAAAAAAAAAAAAAAAAAA6JQoASzggXDkkCfxDLxb/bF1J/4ByYf+NgXL/lop8/62kmf/g3dn///////j3 - 9v/f29f/2NTP/8/KxP/CvLT/pZyQ/5WKfP+Wi33/mo+C/4l9bf9HNBv/NyIG/zolCvxLOCBcOiUKAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBdTDYkQCwS4jciB/81HwT/NR8E/zciB/88Jw3/QS0T/1pI - Mv+UiHr/cGBN/0s4H/9KNx//cGBN/5aLff9fTzn/RzMa/0c0G/9BLRT/NyIH/zciBv84Iwj/QCwS4l1M - NiRdTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi39wAI2AcgVWRC1lSDUcqEMvFs0/KxHqOiUK/Dgj - CP84Igf/NyEG/zUgBf81IAT/NyIH/zciB/81IAX/NSAF/zYhBv83Igf/NyIH/zolCvw+KhDqQS0UzUYy - GahUQiplkIR1BYyAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJuRhACzq6IDeGpYEWNT - PipCLhV5OiUK8jgjCP84Iwj/OCMI/zgjCP84Igf/OCIH/zgjCP84Iwj/OCMI/zgjCP85JAryQi4UeWFQ - OypvX0wRpJqOA4x/cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzgfAFVDKwRCLhRtOiYL8jgjCP84Iwj/OiUL+0EsE+dBLRPnOyYL+zgjCP84Iwj/OiUL8kEt - E21RPicESTUdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAU0EqAGBPOgRFMhl9QS0T7Ek2HblNOiJpZFQ/MGhYQzBRPidpSzkguUIu - FOxEMRd9XEw2BFE/KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+B///wAA//4A - AH/8AAA//AAAP/AAAA/AAAADwAAAA8AAAAPAAAADwAAAA4AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACAAAABAAAAAAAAAAAAAAAAAAAAAIAAAAHgAAAH4AAAB/AAAA/wAAAP8AAAD/wAAD//gAH//8AD/ygA - AAAwAAAAYAAAAAEAIAAAAAAAACQAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGxc - SACShXcEaVlFT0o3HstINBzLZFM+T4R3ZwRkUz4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACXjH4AAAAAAGdXQyNRPiaTQCsR6zkkCf85JAn/PyoQ6048JJR+cF8v////AIl8bAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1c/OAGtcSAB0ZVMib2BMU4F0 - Y1OMgHBJSTYdIT4pDx1rW0cmXUw2dEIuFNY6JQr+OCMH/zgjCP84Iwj/OCMI/zolC/9HMxrgW0kzdWtb - RyY+KQ8dSTYdIYyAcEmBdGNTb2BMU3RlUyJrXEgAysjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfW9eAIR3 - Zw1MOSGsQS0T/0UxGP5FMRj4OSQK4zolC+FBLRPmPSkO/DgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP83Igf/PSgO/EEtE+Y6JQvhOSQK40UxGPhFMRj+QS0T/0s5IKyEd2YNfW9dAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACgl4sAAAAAAGFQOmw9KA76NyIH/zciB/83Igf/OCMI/zgjCP84Iwf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjB/84Iwj/OCMI/zciB/83Igf/NyIH/z0oDvpgTzpsAAAAAKKY - jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABXRS4Aa1tHJEQxF9U4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zYhBf82IQX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9EMBfValpGJFZELQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCWiQDEvbUHVEIrmjolCv84Iwj/NiEF/zUgBP81IAT/NSAE/zUg - BP83Igf/OCMI/zgjCP82IQb/OSQJ/2BPOf9gUDr/OSQJ/zUgBP81IAT/NSAE/zUgBP81IAT/NSAE/zUg - BP81IAT/NiEG/zgjCP86JQr/VEEqmsG6sweek4cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl41+AODd2ARtXUleQi4U9DciB/85JAn/YE86/31v - Xf98b13/fG9d/3lrWf9KNx//NB8D/zYgBf9PPSX/n5WI/+zq5//t6+j/oZeK/21dSv94aVf/fG9d/3xv - Xf98b13/fG9d/3ttXP9wYU7/VUMs/zomC/82IQb/Qi4U9GxdSV7h3dkEl41+AAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AGtaRgCNgHAFY1I9OlNBKp9EMBbvOCMI/zgj - CP8+Kg//wbuy/7Wto/+il4v/oZeL/8nEvf+so5j/alpG/4l9bf/b19L//v79////////////7Orn/763 - rv+onpP/opiM/6OYjP+jmIz/opiM/6Sajv+wp53/w721/6ifk/9OOyP/NiEG/0QwFu9TQiqfY1M+Oo2A - cQVrW0cA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wANAAAAqKCUDlxKND9JNh2fQCwS7Tkk - Cf83Igf/OCMI/zgjCP8/KxH/wLqx/11MNv81IAT/NR8E/4+DdP/+/v7/+Pf2//v7+/////////////// - ///Qy8T/Y1I9/z0pD/84Iwj/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TToh/5qPgv+2rqT/RzQb/zYh - Bv85JAn/QCwS7Uc0G59ZRzE/p52RDh4IAAD08e8AAAAAAAAAAAAAAAAAAAAAAJ+WiQCjmo0YX085sj8r - EfM4Iwj/OCMH/zgjCP84Iwj/OCMI/zgjCP8/KxH/wLqx/15NN/82IAX/NSAF/4+DdP////////////// - /////////////+zq5/9jUj3/NB8D/zgjCP84Iwj/OCIH/zgjB/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsm - C/+upZr/in5u/zYgBf84Iwj/OCMH/zgjCP8/KxHzXkw3sqGXihiflIcAAAAAAAAAAAAAAAAAAAAAAIF0 - YwB/cmEtSDUc7TchBv84Iwj/OCMI/zgjCP84Iwj/OCMI/zciB/89KQ7/wLmw/15NN/82IAX/NSAF/4+D - dP///////////////////////////8C6sf88Jw3/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9sXEj/tKyi/zwoDv84Iwj/OCMI/zgjCP83IQb/SDUc7YByYS2CdGQAAAAAAAAA - AAAAAAAAAAAAAFZFLgBWRC0tPysR7TgjCP84Iwj/OCMI/zgjCP83Igf/NSAE/zwoDf9rXEj/1NDK/11M - Nv82IAX/NSAF/4+DdP///////////////////////////6KYi/82IQb/NyIG/0s4IP+6s6n/y8W+/8rE - vP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYhBv9RPyj/vLSr/0g0HP83Igf/OCMI/zgjCP84Iwj/PysR7VZE - LS1XRS4AAAAAAAAAAAAAAAAAAAAAAEEtEwBINBsxOSUK7zgjCP84Iwj/OCMI/zgjB/9ALBL/cGFO/7ix - p//y8O7/8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////5aKfP8yHAH/Mx0C/089 - Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9KNx//vreu/1NBKv83IQb/OCMI/zgj - CP84Iwj/OSUK70c0GzFBLRMAAAAAAAAAAAAAAAAAAAAAAGVUPwB+cF5eQCwS/jgiB/84Iwj/OCMI/zYg - Bf+GeWj/9/b1////////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ix - p/93aFb/d2lX/4p+bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9INBv/1tHM/4p9 - bf81IAX/OCMI/zgjCP84Igf/QCwS/n5wXl5lVD8AAAAAAAAAAAAAAAAAAAAAAFpJMwBsXEhpPysR/zgj - CP84Iwj/OCMI/zYhBv+glYj/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////7+/v/+/f3//v79//7+/v////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/3NjT/6GYi/82IQb/OCMI/zgjCP84Iwf/PysR/2xdSWlbSTMAAAAAAAAAAAAAAAAAn5WIAAAA - AABPPSV9OiYL/zgjCP84Iwj/OCMI/zgjCP+tpZr/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/3NjT/6+nnP84Iwj/OCMI/zgjCP84Iwj/OiYL/089JX0AAAAAn5SIAAAA - AAD///8AY1M+AG5fS0BFMRjnNyIH/zgjCP84Iwj/OCMI/zkkCf+8taz/////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+/uLD/PCgN/zciBv9HNBv/3dnU/764r/85JAn/OCMI/zgjCP84Iwj/NyIH/0Ux - GOdsXUlAYlE8AP///wB7bVsAgHJhH0s4IMY4Iwj/OCMI/zgjCP84Iwj/NyIH/0MvFf/OycP///////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////+/ubD/PCgN/zciBv9HNBv/3NjT/9HMxf9DLxX/NyIH/zgj - CP84Iwj/OCMI/zgjCP9LOCDGf3JgH3ttWwCimYwNUkAomjolC/84Iwj/OCMI/zgjCP84Iwj/OCMI/5CE - df/6+vn/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////++t67/PCcN/zciBv9HNBv/2tbQ//38 - /P+RhXb/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQv/UT8omqCWiQ1ZSDJ6PioQ9jgjB/84Iwj/OCMI/zgj - CP82IAX/bl9L/+3r6f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+qoZX/OCMI/zci - B/9HNBv/2dXQ///////u7Or/cWJO/zYgBf84Iwj/OCMI/zgjCP84Iwf/PioP9llHMXpKNx/tNyIH/zgj - CP84Iwj/OCMI/zYhBv9UQiv/19LN////////////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9PO - yP9fTjj/NiEG/zUgBP9PPCT/4t/c////////////2NTP/1RCK/82IQb/OCMI/zgjCP84Iwj/NyIH/0s3 - H+1XRS+1OiUK/zgjCP84Iwj/OCMI/zUgBP9uX0z/9/b1////////////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP///////////////////////////////////////////+Th3v91Z1T/aFhE/2lZ - Rf9pWUX/ZlVB/0o3Hv83Igf/OyYL/0s4IP+flYj//Pv7////////////9/b1/25fTP81IAT/OCMI/zgj - CP84Iwj/OiUK/1dGL7VrW0hMQCsS7zgjB/84Iwj/OCMI/zciB/9FMRj/zsnC//////////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////////////////////9vX - 0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c1//8+/v/////////////////zsnC/0Ux - GP83Igf/OCMI/zgjCP84Iwf/QCsR72tbR0yglooMUj8ouTgjCP84Iwj/OCMI/zgjCP81IAX/iX1t//7+ - /f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - /////////////9vX0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c2P/8+/v///////// - ///+/f3/iX1t/zUgBf84Iwj/OCMI/zgjCP84Iwj/Uj8ouaGXiwwbAwAAZVVAYD0pD/o4Iwj/OCMI/zgj - CP82IQb/UD4m/+Dd2P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - /////////////////////////////+Th3v91Z1T/aFhE/2lZRf9pWUX/ZlVB/0k2Hv83Igf/OyYL/0s4 - IP+glon//Pv7///////g3Nj/UD4m/zYhBv84Iwj/OCMI/zgjCP89KQ/6ZVVAYBsDAABtXUoAcWNPE0Et - FMY4Iwj/OCMI/zgjCP84Iwf/PSkP/8S+tv//////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9LO - yP9eTjj/NiEG/zUgBP9PPCT/4+Dc///////Evrb/PSkP/zgjCP84Iwj/OCMI/zgjCP9BLRTGcWJQE21d - SgCwp5wAxb63C1I/KL83Igf/OCMI/zgjCP83Igf/RzMa/9nW0P//////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+qoZX/OCMI/zciB/9HNBv/2dXQ///////Z1dD/RjMa/zciB/84Iwj/OCMI/zci - B/9SQCi/xsG4C7CpngByYk8AdmdVFUUxGNU4Iwj/OCMI/zgjCP82IQb/WEYw/+vp5v////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////++t6//PCcN/zciBv9HNBv/2dXQ///////r6eb/WEYv/zYh - Bv84Iwj/OCMI/zgjCP9FMhjVdmhVFXFjUAB+cF4Af3FgNkUxGPA3Igf/OCMI/zgjCP81IAT/b2BN//n5 - +P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////+/ubD/PCgN/zciBv9HNBv/2dXQ//// - ///5+fj/b2BN/zUgBP84Iwj/OCMI/zciB/9FMRjwf3JgNn5wXwBKNh4AXUw2Sj0oDvk4Iwj/OCMI/zgj - CP82IAX/bF1J/+7s6v//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/2dXQ///////v7ev/bV1J/zYgBf84Iwj/OCMI/zgjCP89KA75XUw2Sko3HgA6JgsAY1M+ejwn - Df84Iwj/OCMI/zgjCP84Iwj/OiUL/3lqWP/j4N3/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/2tbR/+bk4P96bFr/OyYL/zgjCP84Iwj/OCMI/zgjCP88Jw3/ZFM+ejsm - CwAAAAAATz0ljDkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zYhBv9hUDr/zsnC//7+/v//////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////7+/v/+/f3//v79//7+/v////////////// - //////////////////+/uLD/PCgN/zciBv9JNR3/x8G5/21dSf82IAX/OCMI/zgjCP84Iwj/OCMI/zgj - CP85JAn/Tz0ljAAAAAAAAAAAcGFOREo2HtQ4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP83Igf/emxa//Dv - 7f//////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ixp/93aFb/d2lX/4p+ - bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9JNh3/urKo/047I/83IQb/OCMI/zgj - CP84Iwj/OCMI/zgjCP9JNh3Ubl9LRAAAAADU0MsAAAAAAGFRPCROOyO3OiYL/TgjB/84Iwj/OCMI/zci - B/9KNh7/x8G5/8zGv//X087/2tbR/1hHMP82IQX/NSAF/4+DdP///////////////////////////5SJ - e/8yHAH/Mx0C/089Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9LNx//u7Oq/088 - JP83Igb/OCMI/zgjCP84Iwf/OiYL/U47I7dhUTwkAAAAANTQywAAAAAAxL61AEUxGQCAcmEUSzggiz8r - Efo4Iwj/OCMI/zciBv9PPSX/u7Oq/048JP9EMRf/UD4m/z4qEP84Iwj/NSAF/4+DdP////////////// - /////////////52Thv82IQb/NyIG/0s4IP+6s6n/y8W+/8rEvP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYh - Bv9RPyj/vLWs/0g1HP83Igf/OCMI/zgjCP8/KxH6Sjcfi31vXRRDLhUAxL61AAAAAAAAAAAAAAAAAAAA - AACFeGgAnJOFCmRUP3w+KhDzOCMI/zciBv9PPCX/urKp/0k2Hf82IQb/NiEG/zgjCP84Iwj/NSAF/4+D - dP///////////////////////////7qyqf86JQr/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9rXEj/tK2i/zwoDv84Iwj/OCMI/z4qEPNjUz58mY+BCoJ2ZQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAjIBwAJyShAhJNh61OCMI/zciBv9PPSX/urKp/0c0G/80HgP/NSAF/zYh - Bv83Igf/NSAE/4+DdP///////////////////////////+Xi3/9ZSDH/NB8E/zgjCP84Iwj/OCIH/zgj - B/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsmC/+tpZr/in5u/zYgBf84Iwj/OCMI/0o3HrWckoUIjIBwAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJGEACgRAABUQituOyYL/zciB/9KNh7/x8G5/6GX - iv+BdGP/bV5K/1hHMP9KNx7/OycM/5CEdf/////////////////////////////////Fv7f/XUw2/zwo - Df83Igf/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TDoh/5qPgf+2rqT/RzQb/zciB/84Iwj/OyYL/1RC - K24oEQAAnJKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBiUjwtQCsR6Dci - B/86JQr/ZVVA/4p+bv+imIz/same/722rf+/uK//uLGn/9XRy//5+Pj///////////////////////z8 - +//9/fz/7evo/8bAuP+so5j/p52S/6Sajv+imIz/opiM/6Sajv+wp5z/w721/6ifk/9OOyP/NiEG/zgj - CP84Iwj/QCsR6GJSPC1dTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHhq - VwCNgnIIRTIYszgjCP84Iwj/NiAF/zUgBP82IQb/OyYM/0MwFv9QPSb/ZFQ//3FiT/9+cF//4d3Z//f2 - 9f/c2NP/saie/31vXf98b13/rqWa/9vY0//5+Pf/4+Hd/4x/cP97blz/fG9d/3ttXP9wYU7/VUMs/zom - C/83Igb/OCMI/zgjCP84Iwj/RTIZs46Dcwh4a1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAMG5sAAvGQAAXEo0ejsmDP83Igf/NyIH/zgjCP84Iwj/OCMI/zciB/83IQb/NSAF/zUg - BP82IQX/YE85/21eSv9JNh3/OSQJ/zUgBP81IAT/OCMI/0g0HP9rW0f/YE86/zYgBf81HwT/NSAE/zUg - BP81IAT/NiEG/zgjCP84Iwj/NyIH/zciB/87Jgz/XEs1ei4YAADEvrQAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYRjAAbl5LLFNBKrZEMRfaQi4U9DwnDfw5JQr/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/NiEF/zUgBf83Igf/OCMI/zgjCP84Iwj/OCMI/zciB/81IAX/NiEF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf87Jgz8QCwS9EIuFdpRPie2bl9MLFhHMQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARTEYALGpngp6bFoaa1xIO2JS - PWRRPyiMTjsjvzolC/A4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQrwTjsjv088JIxdSzVkZFM+O3FiTxqil4sKNCAGAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAPb19AD///8BnpOGCEIuFWY7JgzwOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsmC/BBLRRmnZOGCP///wH29fQAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkMrAG5dSQJGMxlmPCcN7zgjCP84Iwj/OCMI/zgj - CP84Iwf/OCMI/zsmC/87Jgv/OCMI/zciB/84Iwj/OCMI/zgjCP84Iwj/PCcM70QwF2ZmVkMCUT8oAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFlHMABxYU4CSTYdZT0o - Du84Igf/OCMJ/zwnDP1AKxHmSTYdvV1MNoleTTiJTDkhvUEtE+Y8KA39OCQJ/zciB/88KA3vSDUcZWlb - RgJUQywAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AABjUj0Af3FiA007InRFMhjvTjsjw11NN3xlVUAyi39vC////wH///8BlIl7C21dSTJjUz58UT8ow0cz - Gu9MOCB0eGpWA19ONwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///gf//8AAP//8A///wAA//AAAA//AAD/4AAAB/8AAP/gAAAH/wAA/8AAAAP/ - AAD/gAAAAf8AAP8AAAAA/wAA/AAAAAA/AADwAAAAAA8AAOAAAAAABwAA4AAAAAAHAADgAAAAAAcAAOAA - AAAABwAA4AAAAAAHAADgAAAAAAcAAOAAAAAABwAAwAAAAAADAACAAAAAAAEAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA - AAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAMAAAAAAAwAA4AAAAAAH - AADwAAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAA/gAAAAB/AAD+AAAAAH8AAP8A - AAAA/wAA//AAAA//AAD//AAAP/8AAP/+AAB//wAA//8AAP//AAAoAAAAoAAAAEABAAABACAAAAAAAACQ - AQATCwAAEwsAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AHNjUf9zY1H/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Am5CD/z4pD/84Iwj/OCMI/zsnDP+NgXL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/UD0m/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0w4IP+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBvXkv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2dX - RP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI2Bcv87Jgv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/4FzYv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Atq2j/0k1Hf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/6eek/////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AY1Q//zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/X085/9DMxv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCNgXL/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/gXNi/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/9DLxf/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9AKxL/m5CD//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ApJuQ/3ttW/+Ie2r/kIN0/5eMfv+il4z/q6OW/7Ssov+9tq3/yMK6/9DMxv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AysS+/1hHMP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9YRzD/ycS9/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDQzMb/yMK6/722rf+0rKL/q6OW/6KXjP+XjH7/kIN0/4h7 - av97bVv/pJuQ/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9CLhT/Sjcf/1NA - KP9fTzn/Xk02/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/X085/1NAKP9KNx//Qi4U/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zolCv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGJRPf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKmg - lP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBDLxf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wB6alj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP96bFr/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDHwrn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/8O9tf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AU0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5eMfv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKq - oP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/sqqg//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBINRz/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wCDd2X/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OSQJ/1dGLv/Iwrr/yMK6/1dGLv84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/gnRk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS/5uQg/////////////// - ////////p56T/0IuFP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/MxsD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AWEcw/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/emxa////////////////////////////////////////////iHtq/zkk - Cf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8An5aI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZW - Qv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/dGRT/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/0MzG//// - ///////////////////////////////////////////////////QzMb/Xk02/zgjCP84Iwj/Qi4U/1xL - Nf9pWUX/dGRT/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3ps - Wv96bFr/emxa/3psWv9uYEz/ZlVB/048JP86JQr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Av7ev/z8qEP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - ////////////////////////////////////////////////////////RjMZ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf+tpJj///////////////////////////////////////// - ///////////////////////////////////KxL7///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////8/Lxf+hl4n/Y1M+/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PyoQ/7+3r/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wC0rKL/V0Yu/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////////////////////////////////////////////////// - /////////////////////////////3xuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/4l+ - bv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////iX5u/zsnDP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/V0Yu/7Ss - ov////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9OPCT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP/JxL3///////// - //+Ie2r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZlZC//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////8O9 - tf+2raP/qJ2S/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KX - jP+il4z/opeM/6Sajv+yqZ//wLmx/87Iwv//////////////////////////////////////altG/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk/6eek/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Am5CD/0c0G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy///////////////////////Szsj/ycS9/722 - rf+2raP/wryz//////////////////////////////////////////////////////////////////// - /////////////////////////////7Cnnf92Z1X/RjMZ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/1VDLP+Qg3T///////////////////////////+ajoH/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RzQb/52RhP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCQg3T/QS0T/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////pp2R/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r//////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/5CEdf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AI2Bcv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsnDP+kmo7///////// - ////////lIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PCgN/4FzYv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AgXNi/zol - Cv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////VUMs/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////////9mVUH/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/3Nl - Uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMnEvf9sXEj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ZlZC/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/21dSf/KxL7/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Awry0/2NT - Pv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////pp2R/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv////////////////9/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1xLNf+2raP/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AubOq/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////0c0 - G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/dGZU/////////////////zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7mxqP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AKmglP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9eTTb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////9lVUD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/k4d3////////////m5CD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AjIBx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////PCgN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1VELf///////////9HM - x/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AHtuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OSQJ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9/cmD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBvXkv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////6adkf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/PCgN/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/YlI8/0MvF/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8S+t////////////1dGLv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/b15L/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AX085/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+PgnT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - ////////////////////////////////////////////////////////////////////////wbux/1E+ - J/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+onZL///////// - //9wYU3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/19POf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFA+Jv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/q6OW//////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////gnRk/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPib/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBCLhT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1pIMf+2raP///////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////3ZnVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8jCuv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv///////////5CEdf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/7ato////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWkX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////UT4n/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Bc2L///////// - //+YjYD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9wYU3/zsjC//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OiUK/3xuXf////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2hXQ/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////7KqoP86JQr/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDPycL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9oV0P/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/PycL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AwLmx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/1tKNf////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////9bSTT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/wLmx/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKpn/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////aVpF/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7KqoP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCimY3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/empY//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4d7a/////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+GeWj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/lIp7/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIV3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+XjH7///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////lot8/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4Z5aP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB1aFT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pp2R//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////6SbkP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AZ1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Ssov////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+yqqD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEYzGf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/CvLT///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////wry0/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AG9eS/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0czH//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/altG/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJuQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+bkIP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AMzGwP86JQr/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/8jC - uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBMOCD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9JNR3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBsXEj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/RTEY//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////QzAW/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/2lZRf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCXjH7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1RC - Kv////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////1NAKP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/mI2A/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wDGwLj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+elIb///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////+elIb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf/GwLj/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ARzQb/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zkkCf9sXEj///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////25g - TP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZlZC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9KNx////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////TToi/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9iUT3/////AP///wD///8A////AP///wD///8A////AP///wD///8Ak4d3/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jwz/zsjC//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3dl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////9POyP88KA3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5KH - eP////8A////AP///wD///8A////AP///wD///8Aw762/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/opmN//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////opeM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wLmx/////wD///8A////AP// - /wD///8A////AEYzGf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cWJP//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9iUjz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////92Z1X/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0UxGP////8A////AP///wD///8A////AGNUP/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////1E+J/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/YVA7/////wD///8A////AI2Bcv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OycM/9DM - xv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////////////////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+PgnT/////AMK8 - tP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////1pIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3tuXP///////////////////////////////////////////////////////////6ad - kf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7+3r/9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////4l8 - bP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+QhHX///////// - ////////////////////////////////////////////////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9TQCj/lYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1A9Jv////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3tuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wryz//////////////////////////////////// - //////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/l4x+//// - /wA7Jwz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //+5s6r/hnlo/0ArEv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/UT4n//////////////////////////////////////////////////////////////////// - /////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/////wD///8Aemxa/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/w721//////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/8nEvf////////////// - /////////////////////////////////////////////////////////////8K8tP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3ttW/////8A////AM7Iwv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29eS/////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QCsS/7u1rP////////////////////////////////////////////// - //////////////////////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Py8X/////AP// - /wD///8AYU86/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9HNBv/hnlo//// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/////AP///wD///8A////ALSsov84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4p+cP////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/XEs1/722rf////////////////////////////////////////////// - ////////////////////////////////////////////////////////iX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/tKyi/////wD///8A////AP///wD///8ASTYe/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - /////////////////////////////0EtE/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////wD///8A////AP// - /wD///8A////AJiNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3///////////////////////// - /////////////////////////////////////////////////////////////////////////////6Sb - kP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5qOgf////8A////AP///wD///8A////AP///wD///8AOycM/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9QPib///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/fG5d//////////////////////////////////////////////////// - //////////////////////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwo - Df////8A////AP///wD///8A////AP///wD///8A////AHxuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/8G7 - sf////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - ///////////////////AubH/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3/////AP///wD///8A////AP// - /wD///8A////AP///wDSzsj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9sXEj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/vbat//////////////////// - ////////////////////////////////////////////////////////////////////////aVpF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/087I/////wD///8A////AP///wD///8A////AP///wD///8A////AGJS - PP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/4d7a/////////////////////////////// - /////////////////////////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wC2r6T/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+He2v///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/722rf////////////////////////////////////////////// - /////////////4Z6af84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7avpP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEo3H/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP87Jwz/ysS+//////////////////////////////////////////////////////9AKxL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0k2Hv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCbkIP/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/4J0ZP8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1NAKP////////////// - //////////////////////////////////+il4z/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+bkIP/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/bmBM//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3RmVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wry0//////////////////////////////////// - ////////bmBM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP88KA3/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/5GGdv///////////////////////////////////////////4yAcf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/f3Fh/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+vp5z///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP97blz///////// - //////////////////////////////////+tpJj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/0MzG//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubOq/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////zsjC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDMxsD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/zcjB/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Aq6GV/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0w4IP////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////Y1Q//zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////Sjcf/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6ujlv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIl8bP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Jfm7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBoV0P/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/joRz//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////+NgXL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ARzQb/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Cnnf////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////r6ec/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/RzMf///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMrEvv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////OSQJ/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8rGv/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/TToi//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////0w4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+poJT/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiHtq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29e - S/////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - //////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/h3tr/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGZVQf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Qg3T///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////joRz/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBGMxn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////6idkv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////722rf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AycS9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5SKe/////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////////////////////6KXjP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP/KxL7/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cGFN//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////3VoVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qaCU//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCGemn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9UQir///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////1hH - MP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4h7av////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0Iu - FP/CvLP///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////x8C4/0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9mVUH/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEUx - GP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/6KXjP////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////p56T/zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QzAW//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fnFf//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////q6OW/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP// - /wD///8A////AP///wDIwrr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9fTjf///////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/yMK6/////wD///8A////AP///wD///8A////AP///wD///8A////AHxu - Xf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2 - Hv/Py8X//////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fG5d//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Ap56T/zolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////Z1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDEvrf/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9RPif/hnlo/3xuXf////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2VVQP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9DLxf/xL63/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBVRC3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9QPSb///////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9nV0T/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9VRC3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AHNj - Uf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9zY1H/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Alot8/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////8G7sf+7s6v///////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2dXRP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9QPSb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4FzYv///////////5iNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Wi3z/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wC7s6v/PyoQ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/RjMZ/2JSPP98bl3/mI2A/7Ssov/OyML//////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWUX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///JxL3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //+QhHX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/z4pD/+2raP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBPPCX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////dGZU/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/088 - Jf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AGpb - Rv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////4J0ZP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////CvLT/U0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/6idkv///////////3BhTf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZVQf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+OhHP/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwoDf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/2JRPf9FMRj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Dvrb///////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4l+bv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCvp5z/PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OyYL/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/6mglP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AM/Lxf9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VUMs////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/STYe/8/Lxf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFVE - Lf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////Qi4U/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5GGdv///////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////39yYP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9fTzn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6mglP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////OyML/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9zZVL/////////////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBiUT3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////3Bh - Tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP89KQ7/////////////////gXNi/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9jVD//////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ApJqO/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////Qy8X/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/pp2R/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pJuQ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8rEvv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//// - /////////////2ZWQv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AX043/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Sh3j///////// - ////////wry0/6adkf+JfGz/bV1J/1A9Jv85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////z8nC/0k2Hv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/opeM/////////////////5aLfP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/19ON/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJ+WiP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZVVA//////////////////////////////////// - ///////////////////AubH/pJqO/4Z6af9qW0b/TToi/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////hnlo/zsm - C/84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b//////////////////////6ujlv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+PgnT///////////////////////////////////////////////////////// - /////////////////////////////722rf+gl4v/g3hm/2hXQ/9JNh7/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////npSG/2lZRf8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUMs/4+CdP///////////////////////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP8+KQ//W0k0/3doVf+Uinv/sKme/87Iwv////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////AubH/sqqg/6adkf+il4z/opeM/6KX - jP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+imY3/sKme/7+3 - r//OyML//////////////////////////////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Wkgx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0EtE/9eTTb/emxa/5eMfv+0rKL/0czH//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////jIBx/zwoDf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5mQgv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2FPOv98bl3/mZCC/7ewpv////////////////////////////// - //+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/6KZjf9lVUD/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBUQir/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/Y1M+/3VoVP9INRz/OCMI/6ujlv////////////// - /////////////////////////////////////////////////////////////62kmP9xYk//PyoQ/z8q - EP9xYk//raSY//////////////////////////////////////////////////////////////////// - ////////sKme/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/bmBM/2ZV - Qf9OPCT/OyYL/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9UQir/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AlIh5/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////// - //////////////////+il4z/ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/WEcw/5SK - e//Py8X/////////////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/lot8/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA5JAn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+elIb////////////////////////////CvLT/iHtq/006Iv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0c0G/96bFr/tq2j//// - ////////////////////////pJqO/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8ATzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/52R - hP//////r6ec/3NlUv89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/ZlVB/6KXjP//////opmN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/048JP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI+C - dP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+Qg3T/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0MzG//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCKfnD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNR3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2ZVQf+NgXL/tq2j/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9DLxf/ZlVB/42B - cv+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/0MwFv9mVUH/jYFy/7ato/////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDAubH/mI2A/3Bh - Tf9CLhT/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/3BhTf+YjYD/wLmx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/STYe/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8ATToi/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBRPif/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFNA - KP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tzwl//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AVEIq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/UD0m/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBYRzD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VEIq/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AW0k0/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/V0Yu/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBcTDb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/QCsS/3ZnVf+wqZ7/sKme/3ZnVf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEw2//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGFQO/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2Hv+Bc2L/vbat/////wD///8A////AP// - /wD///8A////AMnEvf+NgXL/Tzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEs1/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AYlE9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zkk - Cf9fTzn/m5CD/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Ap56T/21dSf87Jgv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBjVD//OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD/c2VS/6+nnP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AL22 - rf+Bc2L/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/UD4m/4h7 - av/CvLT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDPy8X/lIp7/1dG - Lv84Iwj/OCMI/zgjCP84Iwj/ZlVB/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AbV9K/19POf+bkIP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9tXUn/bFtH//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - //////////5/////////////////////////+B/////////////////////////gB/////////////// - /////////8AD////////////////////////AAD///////////////////////wAAD////////////// - ////////+AAAD//////////////////////gAAAH/////////////////////4AAAAH///////////// - ////wAf+AAAAAH/gA//////////////AAAAAAAAAAAAD/////////////4AAAAAAAAAAAAH///////// - ////AAAAAAAAAAAAAP////////////8AAAAAAAAAAAAA/////////////gAAAAAAAAAAAAB///////// - ///8AAAAAAAAAAAAAD////////////wAAAAAAAAAAAAAP///////////+AAAAAAAAAAAAAAf//////// - ///4AAAAAAAAAAAAAB////////////AAAAAAAAAAAAAAD///////////4AAAAAAAAAAAAAAH//////// - ///gAAAAAAAAAAAAAAf//////////8AAAAAAAAAAAAAAA///////////gAAAAAAAAAAAAAAB//////// - //+AAAAAAAAAAAAAAAH//////////wAAAAAAAAAAAAAAAP/////////+AAAAAAAAAAAAAAAAf/////// - //gAAAAAAAAAAAAAAAAf////////4AAAAAAAAAAAAAAAAAf///////+AAAAAAAAAAAAAAAAAAf////// - /gAAAAAAAAAAAAAAAAAAf//////4AAAAAAAAAAAAAAAAAAAf/////+AAAAAAAAAAAAAAAAAAAAf///// - AAAAAAAAAAAAAAAAAAAAAP////wAAAAAAAAAAAAAAAAAAAAAP///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///4AAAAAAAAAAAAAAAAAAAAAAH///A - AAAAAAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAB//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAD+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAA4AA - AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAYAA - AAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAA+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAD/AA - AAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH//AAAAAAAAAAAAAAAAAAAAAAAA//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/wA - AAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAAAAAAAB///A - AAAAAAAAAAAAAAAAAAAAAAP//+AAAAAAAAAAAAAAAAAAAAAAB///8AAAAAAAAAAAAAAAAAAAAAAP///8 - AAAAAAAAAAAAAAAAAAAAAD////4AAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAAAAAAAAAAAAAD///// - gAAAAAAAAAAAAAAAAAAAAf/////AAAAAAAAAAAAAAAAAAAAD//////AAAAAAAAAAAAAAAAAAAA////// - 8AAAAAAAAAAAAAAAAAAAD//////4AAAAAAAAAAAAAAAAAAAf//////gAAAAAAAAAAAAAAAAAAB////// - +AAAAAAAAAAAAAAAAAAAH//////8AAAAAAAAAAAAAAAAAAA///////wAAAAAAAAAAAAAAAAAAD////// - /AAAAAAAAAAAAAAAAAAAP//////+AAAAAAAAAAAAAAAAAAB///////4AAAAAAAAAAAAAAAAAAH////// - /gAAAAAAAAAAAAAAAAAAf///////AAAAAAAAAAAAAAAAAAD///////8AAAAAAAAAAAAAAAAAAP////// - /wAAAAAAAAAAAAAAAAAA////////gAAAAAAAAAAAAAAAAAH///////+AAAAAAAAAAAAAAAAAAf////// - /4AAAAAAAAAAAAAAAAAB////////gAAAAAAAAAAAAAAAAAH////////AAAAAAAAAAAAAAAAAA/////// - /8AAAAAAAAAAAAAAAAAD////////8AAAAAAAAAAAAAAAAA//////////gAAAAAAAAAAAAAAB//////// - ///8AAAAAAAAAAAAAD/////////////gAAAAAAAAAAAH//////////////4AAAAAAAAAAH////////// - /////wAAAAAAAAAA////////////////gAAAAAAAAAH////////////////AAAAAAAAAA/////////// - /////+AAAAAAAAAH////////////////8AAAAAAAAA/////////////////4AAAAAAAAH/////////// - //////wAAAAAAAA//////////////////gAAAAAAAH//////////////////AAAH4AAA//////////// - //////+AAH/+AAH//////////////////8AD///AA///////////////////4B////gH//////////// - ///////x/////4////////// - - - \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/ControlPanel/RepoPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/RepoPage.cs deleted file mode 100644 index 5743cc5d17..0000000000 --- a/legacy/V3/TGS.ControlPanel/ControlPanel/RepoPage.cs +++ /dev/null @@ -1,352 +0,0 @@ -using System; -using System.ComponentModel; -using System.Windows.Forms; -using System.Threading; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.ControlPanel -{ - partial class ControlPanel - { - enum RepoAction { - Clone, - Checkout, - Update, - Merge, - Reset, - Test, - Wait, - GenCL, - } - - RepoAction action; - string CloneRepoURL; - string CheckoutBranch; - int TestPR; - - string repoError; - - private void InitRepoPage() - { - RepoBGW.ProgressChanged += RepoBGW_ProgressChanged; - RepoBGW.RunWorkerCompleted += RepoBGW_RunWorkerCompleted; - RepoBGW.DoWork += RepoBGW_DoWork; - BackupTagsList.MouseDoubleClick += BackupTagsList_MouseDoubleClick; - } - - private void BackupTagsList_MouseDoubleClick(object sender, MouseEventArgs e) - { - int index = BackupTagsList.IndexFromPoint(e.Location); - if (index != ListBox.NoMatches) - { - var indexText = (string)BackupTagsList.Items[index]; - if (indexText == "None" || indexText == "Unknown") - return; - var tagname = indexText.Split(':')[0]; - var spaceSplits = indexText.Split(' '); - var sha = spaceSplits[spaceSplits.Length - 1]; - - if (MessageBox.Show(String.Format("Checkout tag {0} ({1})?", tagname, sha), "Restore Backup", MessageBoxButtons.YesNo) != DialogResult.Yes) - return; - - CheckoutBranch = tagname; - DoAsyncOp(RepoAction.Checkout, "Checking out " + tagname + "..."); - } - } - - private void RepoBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - RepoProgressBar.Value = 100; - RepoProgressBar.Style = ProgressBarStyle.Blocks; - RepoPanel.UseWaitCursor = false; - PopulateRepoFields(); - } - - private void RepoRefreshButton_Click(object sender, EventArgs e) - { - PopulateRepoFields(); - } - - private void PopulateRepoFields() - { - if (repoError != null) - MessageBox.Show("An error occured: " + repoError); - - if (RepoBusyCheck()) - return; - - var Repo = Instance.Repository; - - RepoProgressBar.Style = ProgressBarStyle.Marquee; - RepoProgressBar.Visible = false; - RemoteNameTitle.Visible = true; - RepoRemoteTextBox.Visible = true; - BranchNameTitle.Visible = true; - RepoBranchTextBox.Visible = true; - RepoRefreshButton.Visible = true; - SyncCommitsCheckBox.Visible = true; - SyncCommitsCheckBox.Checked = Repo.PushTestmergeCommits(); - - if (!Repo.Exists()) - { - //repo unavailable - RepoRemoteTextBox.Text = "https://github.com/tgstation/tgstation"; - RepoBranchTextBox.Text = "master"; - RepoProgressBarLabel.Text = "Unable to locate repository"; - CloneRepositoryButton.Visible = true; - } - else - { - RepoProgressBarLabel.Visible = false; - - CurrentRevisionLabel.Visible = true; - CurrentRevisionTitle.Visible = true; - IdentityLabel.Visible = true; - MergePRButton.Visible = true; - TestMergeListLabel.Visible = true; - TestMergeListTitle.Visible = true; - UpdateRepoButton.Visible = true; - BackupTagsList.Visible = true; - HardReset.Visible = true; - RepoApplyButton.Visible = true; - TestmergeSelector.Visible = true; - RepoGenChangelogButton.Visible = true; - RecloneButton.Visible = true; - ResetRemote.Visible = true; - TGSJsonUpdate.Visible = true; - - CurrentRevisionLabel.Text = Repo.GetHead(false, out string error) ?? "Unknown"; - RepoRemoteTextBox.Text = Repo.GetRemote(out error) ?? "Unknown"; - RepoBranchTextBox.Text = Repo.GetBranch(out error) ?? "Unknown"; - - var Backups = Repo.ListBackups(out error); - BackupTagsList.Items.Clear(); - if (Backups != null) - { - if (Backups.Count == 0) - BackupTagsList.Items.Add("None"); - else - foreach (var I in Backups) - BackupTagsList.Items.Add(I.Key + ": " + I.Value); - } - else - BackupTagsList.Items.Add("Unknown"); - - var PRs = Repo.MergedPullRequests(out error); - TestMergeListLabel.Items.Clear(); - if (PRs != null) - if (PRs.Count == 0) - TestMergeListLabel.Items.Add("None"); - else - foreach (var I in PRs) - TestMergeListLabel.Items.Add(String.Format("#{0}: {2} by {3} at commit {1}\r\n", I.Number, I.Sha, I.Title, I.Author)); - else - TestMergeListLabel.Items.Add("Unknown"); - } - } - - bool RepoBusyCheck() - { - if (Instance.Repository.OperationInProgress()) - { - DoAsyncOp(RepoAction.Wait, "Waiting for repository to finish another action..."); - return true; - } - return false; - } - private void RepoBGW_ProgressChanged(object sender, ProgressChangedEventArgs e) - { - var val = e.ProgressPercentage; - if (val < 0) - { - RepoProgressBar.Style = ProgressBarStyle.Marquee; - return; - } - RepoProgressBar.Style = ProgressBarStyle.Blocks; - RepoProgressBar.Value = val; - } - - private void RepoBGW_DoWork(object sender, DoWorkEventArgs e) - { - //Only for clones - var Repo = Instance.Repository; - - switch (action) { - case RepoAction.Clone: - repoError = Repo.Setup(CloneRepoURL, CheckoutBranch); - break; - case RepoAction.Checkout: - repoError = Repo.Checkout(CheckoutBranch); - break; - case RepoAction.Merge: - repoError = Repo.Update(false); - break; - case RepoAction.Reset: - repoError = Repo.Reset(true); - break; - case RepoAction.Test: - repoError = Repo.MergePullRequest(TestPR); - break; - case RepoAction.Update: - repoError = Repo.Update(true); - break; - case RepoAction.Wait: - break; - case RepoAction.GenCL: - var result = Repo.GenerateChangelog(out repoError); - if(repoError != null) - repoError += ": " + result; - break; - default: - //reeee - return; - } - - do - { - Thread.Sleep(1000); - RepoBGW.ReportProgress(Repo.CheckoutProgress()); - } while (Repo.OperationInProgress()); - } - - private void CloneRepositoryButton_Click(object sender, EventArgs e) - { - CloneRepo(); - } - - void CloneRepo() - { - CloneRepoURL = RepoRemoteTextBox.Text; - CheckoutBranch = RepoBranchTextBox.Text; - - DoAsyncOp(RepoAction.Clone, String.Format("Cloning {0} branch of {1}...", CheckoutBranch, CloneRepoURL)); - } - private void RecloneButton_Click(object sender, EventArgs e) - { - var DialogResult = MessageBox.Show("This will re-clone the repository, backup, and reset the Static configuration folders. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - CloneRepo(); - } - void DoAsyncOp(RepoAction ra, string message) - { - if (RepoBGW.IsBusy || (ra != RepoAction.Wait && RepoBusyCheck())) - return; - - SyncCommitsCheckBox.Visible = false; - CurrentRevisionLabel.Visible = false; - CurrentRevisionTitle.Visible = false; - TestMergeListLabel.Visible = false; - TestMergeListTitle.Visible = false; - RepoApplyButton.Visible = false; - UpdateRepoButton.Visible = false; - MergePRButton.Visible = false; - CloneRepositoryButton.Visible = false; - RemoteNameTitle.Visible = false; - RepoRemoteTextBox.Visible = false; - BranchNameTitle.Visible = false; - RepoBranchTextBox.Visible = false; - RepoProgressBar.Visible = true; - HardReset.Visible = false; - IdentityLabel.Visible = false; - TestmergeSelector.Visible = false; - RepoGenChangelogButton.Visible = false; - RecloneButton.Visible = false; - ResetRemote.Visible = false; - BackupTagsList.Visible = false; - RepoRefreshButton.Visible = false; - TGSJsonUpdate.Visible = false; - - RepoPanel.UseWaitCursor = true; - - RepoProgressBar.Value = 0; - RepoProgressBar.Style = ProgressBarStyle.Marquee; - - RepoProgressBarLabel.Text = message; - RepoProgressBarLabel.Visible = true; - - action = ra; - repoError = null; - - RepoBGW.RunWorkerAsync(); - } - private void RepoApplyButton_Click(object sender, EventArgs e) - { - var Repo = Instance.Repository; - - if (RepoBusyCheck()) - return; - - var remote = Repo.GetRemote(out string error); - if (remote == null) { - MessageBox.Show("Error: " + error); - return; - } - - var Reclone = remote != RepoRemoteTextBox.Text; - if (Reclone) - { - var DialogResult = MessageBox.Show("Changing the remote URL requires a re-cloning of the repository. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - } - - if (!Reclone) - { - Repo.SetPushTestmergeCommits(SyncCommitsCheckBox.Checked); - var branch = Repo.GetBranch(out error); - if(branch == null) - { - MessageBox.Show("Error: " + error); - return; - } - - CheckoutBranch = RepoBranchTextBox.Text; - if(branch != CheckoutBranch) - DoAsyncOp(RepoAction.Checkout, String.Format("Checking out {0}...", CheckoutBranch)); - } - else - CloneRepositoryButton_Click(null, null); - } - private void UpdateRepoButton_Click(object sender, EventArgs e) - { - DoAsyncOp(RepoAction.Merge, "Merging origin branch..."); - } - - private void HardReset_Click(object sender, EventArgs e) - { - DoAsyncOp(RepoAction.Reset, "Resetting to origin branch..."); - } - - private void ResetRemote_Click(object sender, EventArgs e) - { - DoAsyncOp(RepoAction.Update, "Updating and resetting to remote branch..."); - } - - private void TestMergeButton_Click(object sender, EventArgs e) - { - if (TestmergeSelector.Value == 0) - { - MessageBox.Show("Invalid PR number!"); - return; - } - TestPR = (int)TestmergeSelector.Value; - DoAsyncOp(RepoAction.Test, String.Format("Merging latest commit of PR #{0}...", TestPR)); - } - - private void RepoGenChangelogButton_Click(object sender, System.EventArgs e) - { - DoAsyncOp(RepoAction.GenCL, "Generating changelog..."); - } - - private void TGSJsonUpdate_Click(object sender, EventArgs e) - { - if (MessageBox.Show("This will update the cached TGS3.json to the current repository version, potentially redefining symlinks. Proceed?", "Json Update", MessageBoxButtons.YesNo) != DialogResult.Yes) - return; - var res = Instance.Repository.UpdateTGS3Json(); - if (res != null) - MessageBox.Show(res); - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/ControlPanel/ServerPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/ServerPage.cs deleted file mode 100644 index dee3c04e36..0000000000 --- a/legacy/V3/TGS.ControlPanel/ControlPanel/ServerPage.cs +++ /dev/null @@ -1,499 +0,0 @@ -using Octokit; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows.Forms; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.ControlPanel -{ - partial class ControlPanel - { - bool updatingFields = false; - - /// - /// used for checking the merged state of s - /// - GitHubClient ghclient; - - void InitServerPage() - { - projectNameText.LostFocus += ProjectNameText_LostFocus; - projectNameText.KeyDown += ProjectNameText_KeyDown; - ServerStartBGW.RunWorkerCompleted += ServerStartBGW_RunWorkerCompleted; - ghclient = new GitHubClient(new ProductHeaderValue(Assembly.GetExecutingAssembly().GetName().Name)); - var config = Properties.Settings.Default; - if (!String.IsNullOrWhiteSpace(config.GitHubAPIKey)) - ghclient.Credentials = new Credentials(Helpers.DecryptData(config.GitHubAPIKey, config.GitHubAPIKeyEntropy)); - } - - private void ServerStartBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - if (e.Result != null) - MessageBox.Show((string)e.Result); - LoadServerPage(); - } - - private void ProjectNameText_KeyDown(object sender, KeyEventArgs e) - { - if (e.KeyCode == Keys.Enter) - UpdateProjectName(); - } - - private void CompileCancelButton_Click(object sender, EventArgs e) - { - var res = Instance.Compiler.Cancel(); - if (res != null) - MessageBox.Show(res); - LoadServerPage(); - } - - void LoadServerPage() - { - var RepoExists = Instance.Repository.Exists(); - compileButton.Visible = RepoExists; - AutoUpdateCheckbox.Visible = RepoExists; - initializeButton.Visible = RepoExists; - AutostartCheckbox.Visible = RepoExists; - WebclientCheckBox.Visible = RepoExists; - PortSelector.Visible = RepoExists; - projectNameText.Visible = RepoExists; - CompilerStatusLabel.Visible = RepoExists; - CompileCancelButton.Visible = RepoExists; - CompilerLabel.Visible = RepoExists; - ProjectPathLabel.Visible = RepoExists; - ServerGStopButton.Visible = RepoExists; - ServerStartButton.Visible = RepoExists; - ServerGRestartButton.Visible = RepoExists; - ServerRestartButton.Visible = RepoExists; - PortLabel.Visible = RepoExists; - ServerStopButton.Visible = RepoExists; - TestMergeManagerButton.Visible = RepoExists; - UpdateServerButton.Visible = RepoExists; - RemoveAllTestMergesButton.Visible = RepoExists; - WorldAnnounceField.Visible = RepoExists; - WorldAnnounceButton.Visible = RepoExists; - WorldAnnounceLabel.Visible = RepoExists; - SyncCommitsCheckBox.Visible = RepoExists; - - if (updatingFields) - return; - - var DM = Instance.Compiler; - var DD = Instance.DreamDaemon; - var Config = Instance.Config; - var Repo = Instance.Repository; - - try - { - updatingFields = true; - - ServerPathLabel.Text = "Server Path: " + Instance.ServerDirectory(); - - SecuritySelector.SelectedIndex = (int)DD.SecurityLevel(); - - if (!RepoExists) - return; - - var interval = Repo.AutoUpdateInterval(); - var interval_not_zero = interval != 0; - AutoUpdateCheckbox.Checked = interval_not_zero; - AutoUpdateInterval.Visible = interval_not_zero; - AutoUpdateMLabel.Visible = interval_not_zero; - if (interval_not_zero) - AutoUpdateInterval.Value = interval; - - var DaeStat = DD.DaemonStatus(); - var Online = DaeStat == DreamDaemonStatus.Online; - ServerStartButton.Enabled = !Online; - ServerGStopButton.Enabled = Online; - ServerGRestartButton.Enabled = Online; - ServerStopButton.Enabled = Online; - ServerRestartButton.Enabled = Online; - - switch (DaeStat) - { - case DreamDaemonStatus.HardRebooting: - ServerStatusLabel.Text = "REBOOTING"; - break; - case DreamDaemonStatus.Offline: - ServerStatusLabel.Text = "OFFLINE"; - break; - case DreamDaemonStatus.Online: - ServerStatusLabel.Text = "ONLINE"; - var pc = DD.PlayerCount(); - if (pc != -1) - ServerStatusLabel.Text += " (" + pc + " players)"; - break; - } - - ServerGStopButton.Checked = DD.ShutdownInProgress(); - - AutostartCheckbox.Checked = DD.Autostart(); - WebclientCheckBox.Checked = DD.Webclient(); - if (!PortSelector.Focused) - PortSelector.Value = DD.Port(); - if (!projectNameText.Focused) - projectNameText.Text = DM.ProjectName(); - - UpdateServerButton.Enabled = false; - TestMergeManagerButton.Enabled = false; - RemoveAllTestMergesButton.Enabled = false; - switch (DM.GetStatus()) - { - case CompilerStatus.Compiling: - CompilerStatusLabel.Text = "Compiling..."; - compileButton.Enabled = false; - initializeButton.Enabled = false; - CompileCancelButton.Enabled = true; - break; - case CompilerStatus.Initializing: - CompilerStatusLabel.Text = "Initializing..."; - compileButton.Enabled = false; - initializeButton.Enabled = false; - CompileCancelButton.Enabled = false; - break; - case CompilerStatus.Initialized: - CompilerStatusLabel.Text = "Idle"; - initializeButton.Enabled = true; - compileButton.Enabled = true; - CompileCancelButton.Enabled = false; - UpdateServerButton.Enabled = true; - TestMergeManagerButton.Enabled = true; - RemoveAllTestMergesButton.Enabled = true; - break; - case CompilerStatus.Uninitialized: - CompilerStatusLabel.Text = "Uninitialized"; - compileButton.Enabled = false; - initializeButton.Enabled = true; - CompileCancelButton.Enabled = false; - break; - default: - CompilerStatusLabel.Text = "Unknown!"; - initializeButton.Enabled = true; - compileButton.Enabled = true; - CompileCancelButton.Enabled = true; - break; - } - var error = DM.CompileError(); - if (error != null) - MessageBox.Show("Error: " + error); - } - finally - { - updatingFields = false; - } - } - - private void ProjectNameText_LostFocus(object sender, EventArgs e) - { - UpdateProjectName(); - } - - void UpdateProjectName() - { - if (!updatingFields) - Instance.Compiler.SetProjectName(projectNameText.Text); - } - - private void PortSelector_ValueChanged(object sender, EventArgs e) - { - if (!updatingFields) - Instance.DreamDaemon.SetPort((ushort)PortSelector.Value); - } - - private void ServerPageRefreshButton_Click(object sender, EventArgs e) - { - LoadServerPage(); - } - - private void InitializeButton_Click(object sender, EventArgs e) - { - if (!Instance.Compiler.Initialize()) - MessageBox.Show("Unable to start initialization!"); - LoadServerPage(); - } - private void CompileButton_Click(object sender, EventArgs e) - { - if (!Instance.Compiler.Compile()) - MessageBox.Show("Unable to start compilation!"); - LoadServerPage(); - } - - private void AutostartCheckbox_CheckedChanged(object sender, System.EventArgs e) - { - if (!updatingFields) - Instance.DreamDaemon.SetAutostart(AutostartCheckbox.Checked); - } - private void ServerStartButton_Click(object sender, System.EventArgs e) - { - if (!ServerStartBGW.IsBusy) - ServerStartBGW.RunWorkerAsync(); - } - - private void ServerStartBGW_DoWork(object sender, DoWorkEventArgs e) - { - try - { - e.Result = Instance.DreamDaemon.Start(); - } - catch (Exception ex) - { - e.Result = ex.ToString(); - } - } - - private void ServerStopButton_Click(object sender, EventArgs e) - { - var DialogResult = MessageBox.Show("This will immediately shut down the server. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - var res = Instance.DreamDaemon.Stop(); - if (res != null) - MessageBox.Show(res); - } - - private void ServerRestartButton_Click(object sender, EventArgs e) - { - var DialogResult = MessageBox.Show("This will immediately restart the server. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - var res = Instance.DreamDaemon.Restart(); - if (res != null) - MessageBox.Show(res); - } - - private void ServerGStopButton_Checked(object sender, EventArgs e) - { - if (updatingFields) - return; - var DialogResult = MessageBox.Show("This will shut down the server when the current round ends. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - Instance.DreamDaemon.RequestStop(); - LoadServerPage(); - } - - private void ServerGRestartButton_Click(object sender, EventArgs e) - { - var DialogResult = MessageBox.Show("This will restart the server when the current round ends. Continue?", "Confim", MessageBoxButtons.YesNo); - if (DialogResult == DialogResult.No) - return; - Instance.DreamDaemon.RequestRestart(); - } - - /// - /// Launches the - /// - /// The sender of the event - /// The - void TestMergeManagerButton_Click(object sender, System.EventArgs e) - { - using (var TMM = new TestMergeManager(Instance, ghclient)) - TMM.ShowDialog(); - LoadServerPage(); - } - - /// - /// Calls - /// - /// The sender of the event - /// The - void UpdateServerButton_Click(object sender, EventArgs e) - { - UpdateServer(); - } - - /// - /// Calls with a parameter, re-merging any current s at their current commit, calls and , and starts the prompting the user with any errors that may occur. Merged s are not remerged - /// - async void UpdateServer() - { - try - { - UseWaitCursor = true; - Enabled = false; - try - { - string res = null; - var repo = Instance.Repository; - var pulls = await Task.Run(() => repo.MergedPullRequests(out res)); - - if (pulls == null) - { - MessageBox.Show(res); - return; - } - - List> pullsRequests = null; - if (Program.GetRepositoryRemote(repo, out string remoteOwner, out string remoteName)) - { - //find out which of the PRs have been merged - pullsRequests = new List>(); - foreach (var I in pulls) - pullsRequests.Add(ghclient.PullRequest.Get(remoteOwner, remoteName, I.Number)); - } - - res = await Task.Run(() => repo.Update(true)); - - if (res != null) - { - MessageBox.Show(res, "Error updating repository"); - return; - } - - await Task.Run(() => repo.GenerateChangelog(out res)); - - if (res != null) - MessageBox.Show(res, "Error generating changelog"); - - res = await Task.Run(() => repo.SynchronizePush()); - - if (res != null) - MessageBox.Show(res, "Error synchronizing commits"); - - if (pullsRequests != null) - Task.WaitAll(pullsRequests.ToArray()); - - foreach (var I in pullsRequests) - if (I.Result.Merged) - pulls.RemoveAll(x => x.Number == I.Result.Number); - - var mergeResults = await Task.Run(() => repo.MergePullRequests(pulls, true)); - var compileStartResult = await Task.Run(() => Instance.Compiler.Compile(true)); - - //Show any errors - for (var I = 0; I < mergeResults.Count(); ++I) - { - var err = mergeResults.ElementAt(I); - if (err != null) - MessageBox.Show(err, String.Format("Error re-merging PR #{0}", pulls[I].Number)); - } - - if (!compileStartResult) - MessageBox.Show(res, "Error starting compile!"); - } - finally - { - UseWaitCursor = false; - Enabled = true; - } - } - catch (ForbiddenException) - { - if (ghclient.Credentials.AuthenticationType == AuthenticationType.Anonymous) - { - if (Program.RateLimitPrompt(ghclient)) - UpdateServer(); - return; - } - else - throw; - } - LoadServerPage(); - } - - /// - /// Calls with a parameter, calls , and starts the prompting the user with any errors that may occur. - /// - /// The sender of the event - /// The - async void RemoveAllTestMergesButton_Click(object sender, EventArgs e) - { - try - { - UseWaitCursor = true; - Enabled = false; - try - { - var repo = Instance.Repository; - - var res = await Task.Run(() => repo.Reset(true)); - - if (res != null) - { - MessageBox.Show(res, "Error resetting repository"); - return; - } - - await Task.Run(() => repo.GenerateChangelog(out res)); - - if (res != null) - MessageBox.Show(res, "Error generating changelog"); - - await Task.Run(() => Instance.Compiler.Compile(false)); - if (res != null) - MessageBox.Show(res, "Error starting compile!"); - } - finally - { - UseWaitCursor = false; - Enabled = true; - } - } - catch (ForbiddenException) - { - if (ghclient.Credentials.AuthenticationType == AuthenticationType.Anonymous) - { - if (Program.RateLimitPrompt(ghclient)) - UpdateServer(); - return; - } - else - throw; - } - LoadServerPage(); - } - - private void SecuritySelector_SelectedIndexChanged(object sender, EventArgs e) - { - if (!updatingFields) - if (!Instance.DreamDaemon.SetSecurityLevel((DreamDaemonSecurity)SecuritySelector.SelectedIndex)) - MessageBox.Show("Security change will be applied after next server reboot."); - } - - private void WorldAnnounceButton_Click(object sender, EventArgs e) - { - var msg = WorldAnnounceField.Text; - if (!String.IsNullOrWhiteSpace(msg)) - { - var res = Instance.DreamDaemon.WorldAnnounce(msg); - if (res != null) - { - MessageBox.Show(res); - return; - } - } - WorldAnnounceField.Text = ""; - } - - private void WebclientCheckBox_CheckedChanged(object sender, EventArgs e) - { - if (!updatingFields) - Instance.DreamDaemon.SetWebclient(WebclientCheckBox.Checked); - } - - private void AutoUpdateInterval_ValueChanged(object sender, EventArgs e) - { - if (!updatingFields) - Instance.Repository.SetAutoUpdateInterval((ulong)AutoUpdateInterval.Value); - } - - private void AutoUpdateCheckbox_CheckedChanged(object sender, EventArgs e) - { - if (updatingFields) - return; - var on = AutoUpdateCheckbox.Visible && AutoUpdateCheckbox.Checked; - AutoUpdateInterval.Visible = on; - AutoUpdateMLabel.Visible = on; - if (!on) - Instance.Repository.SetAutoUpdateInterval(0); - else - Instance.Repository.SetAutoUpdateInterval((ulong)AutoUpdateInterval.Value); - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/ControlPanel/StaticPage.cs b/legacy/V3/TGS.ControlPanel/ControlPanel/StaticPage.cs deleted file mode 100644 index 8fef139bc2..0000000000 --- a/legacy/V3/TGS.ControlPanel/ControlPanel/StaticPage.cs +++ /dev/null @@ -1,356 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Windows.Forms; -using TGS.Interface.Components; - -namespace TGS.ControlPanel -{ - partial class ControlPanel - { - IDictionary IndexesToPaths = new Dictionary(); - string originalCurrentFileContent; - IList EnumeratedPaths = new List() { "" }; - bool enumerating = false; - /// - /// Whether or not the static page has been initialized yet - /// - bool initializedStaticPage = false; - - enum EnumResult - { - Enumerated, - Unauthorized, - NotEnumerated, - Error, - } - - void InitStaticPage() - { - if (initializedStaticPage) - return; - if(Instance.Administration == null) - RecreateStaticButton.Visible = false; - BuildFileList(); - initializedStaticPage = true; - } - - void BuildFileList() - { - enumerating = true; - IndexesToPaths.Clear(); - StaticFileListBox.Items.Clear(); - IndexesToPaths.Add(StaticFileListBox.Items.Add("/"), "/"); - if (EnumeratePath("", Instance.Config, 1) == EnumResult.Unauthorized) - { - StaticFileListBox.Items[0] += " (UNAUTHORIZED)"; - IndexesToPaths[0] = null; - } - StaticFileListBox.SelectedIndex = 0; - enumerating = false; - } - - EnumResult EnumeratePath(string path, ITGConfig config, int level) - { - if (!EnumeratedPaths.Contains(path)) - return EnumResult.NotEnumerated; - var Enum = config.ListStaticDirectory(path, out string error, out bool unauthorized); - if(Enum == null) - { - if (unauthorized) - return EnumResult.Unauthorized; - else - { - MessageBox.Show(String.Format("Could not enumerate static path \"{0}\" error: {1}", path, error)); - return EnumResult.Error; - } - } - foreach(var I in Enum) - { - if(I[0] == '/') - { - var dir = I.Remove(0, 1); - var index = StaticFileListBox.Items.Add(DSNTimes(level) + dir + '/'); - var fullpath = path + '/' + dir; - IndexesToPaths.Add(index, fullpath); - switch(EnumeratePath(fullpath, config, level + 1)) - { - case EnumResult.Unauthorized: - StaticFileListBox.Items[index] += " (UNAUTHORIZED)"; - IndexesToPaths[index] = null; - break; - case EnumResult.NotEnumerated: - StaticFileListBox.Items[index] += " (...)"; - break; - case EnumResult.Error: - StaticFileListBox.Items[index] += " (ERROR)"; - IndexesToPaths[index] = null; - break; - } - continue; - } - - IndexesToPaths.Add(StaticFileListBox.Items.Add(DSNTimes(level) + I), Path.Combine(path, I)); - } - return EnumResult.Enumerated; - } - - string DSNTimes(int n) - { - var res = ""; - for (var I = 0; I < n; ++I) - res += " "; - return res; - } - private void StaticFilesRefreshButton_Click(object sender, EventArgs e) - { - BuildFileList(); - UpdateEditText(); - } - - private void StaticFileUploadButton_Click(object sender, EventArgs e) - { - if (StaticFileEditTextbox.Text != "Directory") - { - MessageBox.Show("Please select a directory to upload the file to."); - return; - } - var ofd = new OpenFileDialog() - { - CheckFileExists = true, - CheckPathExists = true, - DefaultExt = ".txt", - Multiselect = false, - Title = "Static File Upload", - ValidateNames = true, - Filter = "All files (*.*)|*.*", - AddExtension = false, - SupportMultiDottedExtensions = true, - }; - if (ofd.ShowDialog() != DialogResult.OK) - return; - - var fileToUpload = ofd.FileName; - - var FileName = Path.Combine(IndexesToPaths[StaticFileListBox.SelectedIndex], Path.GetFileName(fileToUpload)); - - string fileContents = null; - string error = null; - try - { - fileContents = File.ReadAllText(fileToUpload); - } - catch (Exception ex) - { - error = ex.ToString(); - } - if (error == null) - try - { - error = Instance.Config.WriteText(FileName, fileContents, null, out bool unauthorized); - } - catch (Exception ex) - { - error = "Failed to read file, most likely due to it being too large. The transfer limit is much higher on non-remote connections. " + ex.ToString(); - } - if (error != null) - MessageBox.Show("An error occurred: " + error); - BuildFileList(); - } - - private void StaticFileDownloadButton_Click(object sender, EventArgs e) - { - if (StaticFileEditTextbox.ReadOnly) - { - MessageBox.Show("Cannot download this file!"); - return; - } - var remotePath = IndexesToPaths[StaticFileListBox.SelectedIndex]; - if (remotePath == null) - return; - string text, error; - try - { - text = Instance.Config.ReadText(remotePath, false, out error, out bool unauthorized); - } - catch (Exception ex) - { - text = null; - error = "Failed to read file, most likely due to it being too large. The transfer limit is much higher on non-remote connections. " + ex.ToString(); - } - if (text != null) - { - var ofd = new SaveFileDialog() - { - CheckFileExists = false, - CheckPathExists = true, - DefaultExt = ".txt", - Title = "Static File Download", - ValidateNames = true, - Filter = "All files (*.*)|*.*", - AddExtension = false, - CreatePrompt = false, - OverwritePrompt = true, - SupportMultiDottedExtensions = true, - }; - if (ofd.ShowDialog() != DialogResult.OK) - return; - - try - { - File.WriteAllText(ofd.FileName, text); - return; - } - catch (Exception ex) - { - error = ex.ToString(); - } - } - MessageBox.Show("An error occurred: " + error); - } - - private void StaticFileDeleteButton_Click(object sender, EventArgs e) - { - if (MessageBox.Show("Are you sure you want to delete " + ((string)StaticFileListBox.SelectedItem).Trim() + "?", "Confirm", MessageBoxButtons.YesNo) != DialogResult.Yes) - return; - var res = Instance.Config.DeleteFile(IndexesToPaths[StaticFileListBox.SelectedIndex], out bool unauthorized); - if (res != null) - MessageBox.Show(res); - BuildFileList(); - } - - private void StaticFileCreateButton_Click(object sender, EventArgs e) - { - if(StaticFileEditTextbox.Text != "Directory") - { - MessageBox.Show("Please select a directory to create the file in."); - return; - } - var FileName = Program.TextPrompt("Static File/Directory Creation", "Enter the name of the file/directory:"); - if (FileName == null) - return; - - var resu = MessageBox.Show("Is this the name of a directory?", "Directory", MessageBoxButtons.YesNoCancel); - if (resu == DialogResult.Cancel) - return; - var FullFileName = Path.Combine(IndexesToPaths[StaticFileListBox.SelectedIndex], FileName); - if (resu == DialogResult.Yes) - FullFileName = Path.Combine(FullFileName, "__TGS3_CP_DIRECTORY_CREATOR__"); - var config = Instance.Config; - var res = config.WriteText(FullFileName, "", null, out bool unauthorized); - if (res != null) - MessageBox.Show(res); - if (resu == DialogResult.Yes) - { - FullFileName = Path.Combine(FullFileName, "__TGS3_CP_DIRECTORY_CREATOR__"); - config.DeleteFile(FullFileName, out unauthorized); //don't care about this - } - BuildFileList(); - } - - private void StaticFileSaveButton_Click(object sender, EventArgs e) - { - var index = StaticFileListBox.SelectedIndex; - string res; - bool unauthorized; - try - { - res = Instance.Config.WriteText(IndexesToPaths[index], StaticFileEditTextbox.Text, originalCurrentFileContent, out unauthorized); - if (res == null) - originalCurrentFileContent = StaticFileEditTextbox.Text; - } - catch (Exception ex) - { - unauthorized = false; - res = "Failed to write file, most likely due to it being too large. The transfer limit is much higher on non-remote connections. " + ex.ToString(); - } - if (res != null) - { - MessageBox.Show("Error: " + res); - var title = (string)StaticFileListBox.Items[index]; - if (unauthorized && !title.Contains(" (UNAUTHORIZED)")) - StaticFileListBox.Items[index] = title + " (UNAUTHORIZED)"; - } - UpdateEditText(); - } - - private void StaticFileListBox_SelectedIndexChanged(object sender, EventArgs e) - { - UpdateEditText(); - } - - void UpdateEditText() - { - if (enumerating) - return; - var newIndex = StaticFileListBox.SelectedIndex; - if (newIndex == -1) - return; - var path = IndexesToPaths[newIndex]; - var title = (string)StaticFileListBox.Items[newIndex]; - var authed_title = title.Replace(" (UNAUTHORIZED)", "").Replace(" (...)", ""); - if (authed_title[authed_title.Length - 1] == '/') - { - StaticFileEditTextbox.ReadOnly = true; - StaticFileEditTextbox.Text = "Directory"; - if (path != null && path != "/") - { - EnumeratedPaths.Add(path); - BuildFileList(); - enumerating = true; - StaticFileListBox.SelectedIndex = newIndex; - enumerating = false; - return; - } - } - else - { - string entry, error; - bool unauthorized; - try - { - entry = Instance.Config.ReadText(path, false, out error, out unauthorized); - } - catch(Exception e) - { - entry = null; - unauthorized = false; - error = "Failed to read file, most likely due to it being too large. The transfer limit is much higher on non-remote connections. " + e.ToString(); - } - if (entry == null) - { - StaticFileEditTextbox.ReadOnly = true; - StaticFileEditTextbox.Text = "ERROR: " + error; - if (unauthorized && !title.Contains(" (UNAUTHORIZED)")) - StaticFileListBox.Items[newIndex] = title + " (UNAUTHORIZED)"; - } - else - { - StaticFileEditTextbox.ReadOnly = false; - originalCurrentFileContent = entry; - StaticFileEditTextbox.Text = entry.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", Environment.NewLine); - } - } - } - - private void RecreateStaticButton_Click(object sender, EventArgs e) - { - if (!CheckAdminWithWarning()) - { - RecreateStaticButton.Visible = false; - return; - } - if (MessageBox.Show("This will rename the current static directory to a backup and recreate it. Continue?", "Confirm", MessageBoxButtons.YesNo) != DialogResult.Yes) - return; - if (!CheckAdminWithWarning()) - { - RecreateStaticButton.Visible = false; - return; - } - var res = Instance.Administration.RecreateStaticFolder(); - if (res != null) - MessageBox.Show(res); - BuildFileList(); - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/CountedForm.cs b/legacy/V3/TGS.ControlPanel/CountedForm.cs deleted file mode 100644 index 8f79f1c400..0000000000 --- a/legacy/V3/TGS.ControlPanel/CountedForm.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Windows.Forms; - -namespace TGS.ControlPanel -{ - /// - /// Calls when all s are d - /// -#if !DEBUG - abstract -#endif - class CountedForm : ServerOpForm - { - /// - /// The current number of active s - /// - static uint FormCount; - - /// - /// Construct a . Increments - /// - public CountedForm() - { - FormClosed += CountedForm_FormClosed; - ++FormCount; - } - - /// - /// Decrements . Calls if it reaches 0 - /// - /// The sender of the event - /// The - private void CountedForm_FormClosed(object sender, FormClosedEventArgs e) - { - if (--FormCount == 0) - Application.Exit(); - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.Designer.cs b/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.Designer.cs deleted file mode 100644 index 6035c273a7..0000000000 --- a/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.Designer.cs +++ /dev/null @@ -1,176 +0,0 @@ -namespace TGS.ControlPanel -{ - partial class GitHubLoginPrompt - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.PasswordLabel = new System.Windows.Forms.Label(); - this.UsernameLabel = new System.Windows.Forms.Label(); - this.PasswordTextBox = new System.Windows.Forms.TextBox(); - this.UsernameTextBox = new System.Windows.Forms.TextBox(); - this.DividerLabel = new System.Windows.Forms.Label(); - this.APIKeyLabel = new System.Windows.Forms.Label(); - this.APIKeyTextBox = new System.Windows.Forms.TextBox(); - this.OrLabel = new System.Windows.Forms.Label(); - this.LoginButton = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // PasswordLabel - // - this.PasswordLabel.AutoSize = true; - this.PasswordLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.PasswordLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.PasswordLabel.Location = new System.Drawing.Point(12, 41); - this.PasswordLabel.Name = "PasswordLabel"; - this.PasswordLabel.Size = new System.Drawing.Size(92, 18); - this.PasswordLabel.TabIndex = 25; - this.PasswordLabel.Text = "Password:"; - this.PasswordLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // UsernameLabel - // - this.UsernameLabel.AutoSize = true; - this.UsernameLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.UsernameLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.UsernameLabel.Location = new System.Drawing.Point(12, 9); - this.UsernameLabel.Name = "UsernameLabel"; - this.UsernameLabel.Size = new System.Drawing.Size(97, 18); - this.UsernameLabel.TabIndex = 24; - this.UsernameLabel.Text = "Username:"; - this.UsernameLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // PasswordTextBox - // - this.PasswordTextBox.Location = new System.Drawing.Point(132, 41); - this.PasswordTextBox.Name = "PasswordTextBox"; - this.PasswordTextBox.Size = new System.Drawing.Size(233, 20); - this.PasswordTextBox.TabIndex = 23; - this.PasswordTextBox.UseSystemPasswordChar = true; - // - // UsernameTextBox - // - this.UsernameTextBox.Location = new System.Drawing.Point(132, 9); - this.UsernameTextBox.Name = "UsernameTextBox"; - this.UsernameTextBox.Size = new System.Drawing.Size(233, 20); - this.UsernameTextBox.TabIndex = 22; - // - // DividerLabel - // - this.DividerLabel.AutoSize = true; - this.DividerLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.DividerLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.DividerLabel.Location = new System.Drawing.Point(-24, 59); - this.DividerLabel.Name = "DividerLabel"; - this.DividerLabel.Size = new System.Drawing.Size(468, 18); - this.DividerLabel.TabIndex = 26; - this.DividerLabel.Text = "______________________________________________"; - this.DividerLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // APIKeyLabel - // - this.APIKeyLabel.AutoSize = true; - this.APIKeyLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.APIKeyLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.APIKeyLabel.Location = new System.Drawing.Point(12, 95); - this.APIKeyLabel.Name = "APIKeyLabel"; - this.APIKeyLabel.Size = new System.Drawing.Size(78, 18); - this.APIKeyLabel.TabIndex = 28; - this.APIKeyLabel.Text = "API Key:"; - this.APIKeyLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // APIKeyTextBox - // - this.APIKeyTextBox.Location = new System.Drawing.Point(132, 95); - this.APIKeyTextBox.Name = "APIKeyTextBox"; - this.APIKeyTextBox.Size = new System.Drawing.Size(233, 20); - this.APIKeyTextBox.TabIndex = 27; - this.APIKeyTextBox.UseSystemPasswordChar = true; - // - // OrLabel - // - this.OrLabel.AutoSize = true; - this.OrLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.OrLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.OrLabel.Location = new System.Drawing.Point(157, 64); - this.OrLabel.Name = "OrLabel"; - this.OrLabel.Size = new System.Drawing.Size(32, 18); - this.OrLabel.TabIndex = 29; - this.OrLabel.Text = "OR"; - this.OrLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // LoginButton - // - this.LoginButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.LoginButton.Location = new System.Drawing.Point(145, 133); - this.LoginButton.Name = "LoginButton"; - this.LoginButton.Size = new System.Drawing.Size(88, 25); - this.LoginButton.TabIndex = 30; - this.LoginButton.Text = InitalLoginButtonText; - this.LoginButton.UseVisualStyleBackColor = true; - this.LoginButton.Click += new System.EventHandler(this.LoginButton_Click); - // - // GitHubLoginPrompt - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.ClientSize = new System.Drawing.Size(376, 170); - this.Controls.Add(this.LoginButton); - this.Controls.Add(this.OrLabel); - this.Controls.Add(this.APIKeyLabel); - this.Controls.Add(this.APIKeyTextBox); - this.Controls.Add(this.DividerLabel); - this.Controls.Add(this.PasswordLabel); - this.Controls.Add(this.UsernameLabel); - this.Controls.Add(this.PasswordTextBox); - this.Controls.Add(this.UsernameTextBox); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "GitHubLoginPrompt"; - this.ShowIcon = false; - this.ShowInTaskbar = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "Login To GitHub"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label PasswordLabel; - private System.Windows.Forms.Label UsernameLabel; - private System.Windows.Forms.TextBox PasswordTextBox; - private System.Windows.Forms.TextBox UsernameTextBox; - private System.Windows.Forms.Label DividerLabel; - private System.Windows.Forms.Label APIKeyLabel; - private System.Windows.Forms.TextBox APIKeyTextBox; - private System.Windows.Forms.Label OrLabel; - private System.Windows.Forms.Button LoginButton; - } -} \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.cs b/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.cs deleted file mode 100644 index 8d676d1646..0000000000 --- a/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Octokit; -using System; -using System.Threading.Tasks; -using System.Windows.Forms; -using TGS.Interface; - -namespace TGS.ControlPanel -{ - /// - /// Used for recieving a GitHub API key for use in - /// - sealed partial class GitHubLoginPrompt : Form - { - /// - /// The text to appear on - /// - const string InitalLoginButtonText = "Login"; - - /// - /// The to use for OAuth requests - /// - readonly GitHubClient client; - - /// - /// Construct a - /// - /// The to use for requests - public GitHubLoginPrompt(GitHubClient c) - { - InitializeComponent(); - AcceptButton = LoginButton; - DialogResult = DialogResult.Cancel; - client = c; - } - - /// - /// Calls . On success, encrypts it's return value, saves it in and , and closes the . On failure, shows a and exits - /// - /// The sender of the event - /// The - async void LoginButton_Click(object sender, EventArgs e) - { - LoginButton.Text = "Logging in..."; - Enabled = false; - UseWaitCursor = true; - var APIKey = await GetAPIKey(); - if(APIKey == null) - { - MessageBox.Show("Authentication failure!"); - Enabled = true; - UseWaitCursor = false; - LoginButton.Text = "Login"; - return; - } - if (String.IsNullOrWhiteSpace(APIKeyTextBox.Text)) - //They used username authentication, let them know we made a token - MessageBox.Show("A personal access token has been created on your account for use with the Control Panel"); - - //Encrypt it and let's be on our way - var Config = Properties.Settings.Default; - Config.GitHubAPIKey = Helpers.EncryptData(APIKey, out string entropy); - Config.GitHubAPIKeyEntropy = entropy; - DialogResult = DialogResult.OK; - Close(); - } - - /// - /// Retrieves an API key from GitHub based on login information or from the and verifies it - /// - /// The GitHub API key on success, on failure - async Task GetAPIKey() - { - string APIKey; - if (String.IsNullOrWhiteSpace(APIKeyTextBox.Text)) - { - try - { - client.Credentials = new Credentials(UsernameTextBox.Text, PasswordTextBox.Text); - var token = await client.Authorization.Create(new NewAuthorization { Note = "TGS.ControlPanel token to bypass rate limiting" }); - APIKey = token.Token; - } - catch (AuthorizationException) - { - return null; - } - } - else - APIKey = APIKeyTextBox.Text; - - //validate it by pinging a random repository - client.Credentials = new Credentials(APIKey); - try - { - await client.Repository.Get("Dextraspace", "Test"); - } - catch (AuthorizationException) - { - return null; - } - return APIKey; - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.resx b/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.resx deleted file mode 100644 index 1af7de150c..0000000000 --- a/legacy/V3/TGS.ControlPanel/GitHubLoginPrompt.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/InstanceSelector.Designer.cs b/legacy/V3/TGS.ControlPanel/InstanceSelector.Designer.cs deleted file mode 100644 index f9a745d34e..0000000000 --- a/legacy/V3/TGS.ControlPanel/InstanceSelector.Designer.cs +++ /dev/null @@ -1,164 +0,0 @@ -namespace TGS.ControlPanel -{ - partial class InstanceSelector - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(InstanceSelector)); - this.InstanceListBox = new System.Windows.Forms.ListBox(); - this.CreateInstanceButton = new System.Windows.Forms.Button(); - this.ImportInstanceButton = new System.Windows.Forms.Button(); - this.RenameInstanceButton = new System.Windows.Forms.Button(); - this.DetachInstanceButton = new System.Windows.Forms.Button(); - this.RefreshButton = new System.Windows.Forms.Button(); - this.ConnectButton = new System.Windows.Forms.Button(); - this.EnabledCheckBox = new System.Windows.Forms.CheckBox(); - this.SuspendLayout(); - // - // InstanceListBox - // - this.InstanceListBox.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.InstanceListBox.FormattingEnabled = true; - this.InstanceListBox.Location = new System.Drawing.Point(13, 13); - this.InstanceListBox.Name = "InstanceListBox"; - this.InstanceListBox.Size = new System.Drawing.Size(339, 238); - this.InstanceListBox.TabIndex = 0; - this.InstanceListBox.SelectedIndexChanged += new System.EventHandler(this.InstanceListBox_SelectedIndexChanged); - // - // CreateInstanceButton - // - this.CreateInstanceButton.Anchor = System.Windows.Forms.AnchorStyles.Right; - this.CreateInstanceButton.Location = new System.Drawing.Point(358, 15); - this.CreateInstanceButton.Name = "CreateInstanceButton"; - this.CreateInstanceButton.Size = new System.Drawing.Size(148, 25); - this.CreateInstanceButton.TabIndex = 14; - this.CreateInstanceButton.Text = "Create Instance"; - this.CreateInstanceButton.UseVisualStyleBackColor = true; - this.CreateInstanceButton.Click += new System.EventHandler(this.CreateInstanceButton_Click); - // - // ImportInstanceButton - // - this.ImportInstanceButton.Anchor = System.Windows.Forms.AnchorStyles.Right; - this.ImportInstanceButton.Location = new System.Drawing.Point(358, 45); - this.ImportInstanceButton.Name = "ImportInstanceButton"; - this.ImportInstanceButton.Size = new System.Drawing.Size(148, 25); - this.ImportInstanceButton.TabIndex = 15; - this.ImportInstanceButton.Text = "Import Instance"; - this.ImportInstanceButton.UseVisualStyleBackColor = true; - this.ImportInstanceButton.Click += new System.EventHandler(this.ImportInstanceButton_Click); - // - // RenameInstanceButton - // - this.RenameInstanceButton.Anchor = System.Windows.Forms.AnchorStyles.Right; - this.RenameInstanceButton.Location = new System.Drawing.Point(358, 76); - this.RenameInstanceButton.Name = "RenameInstanceButton"; - this.RenameInstanceButton.Size = new System.Drawing.Size(148, 25); - this.RenameInstanceButton.TabIndex = 16; - this.RenameInstanceButton.Text = "Rename Instance"; - this.RenameInstanceButton.UseVisualStyleBackColor = true; - this.RenameInstanceButton.Click += new System.EventHandler(this.RenameInstanceButton_Click); - // - // DetachInstanceButton - // - this.DetachInstanceButton.Anchor = System.Windows.Forms.AnchorStyles.Right; - this.DetachInstanceButton.Location = new System.Drawing.Point(358, 107); - this.DetachInstanceButton.Name = "DetachInstanceButton"; - this.DetachInstanceButton.Size = new System.Drawing.Size(148, 25); - this.DetachInstanceButton.TabIndex = 17; - this.DetachInstanceButton.Text = "Detach Instance"; - this.DetachInstanceButton.UseVisualStyleBackColor = true; - this.DetachInstanceButton.Click += new System.EventHandler(this.DetachInstanceButton_Click); - // - // RefreshButton - // - this.RefreshButton.Anchor = System.Windows.Forms.AnchorStyles.Right; - this.RefreshButton.Location = new System.Drawing.Point(358, 195); - this.RefreshButton.Name = "RefreshButton"; - this.RefreshButton.Size = new System.Drawing.Size(148, 25); - this.RefreshButton.TabIndex = 19; - this.RefreshButton.Text = "Refresh"; - this.RefreshButton.UseVisualStyleBackColor = true; - this.RefreshButton.Click += new System.EventHandler(this.RefreshButton_Click); - // - // ConnectButton - // - this.ConnectButton.Anchor = System.Windows.Forms.AnchorStyles.Right; - this.ConnectButton.Location = new System.Drawing.Point(358, 226); - this.ConnectButton.Name = "ConnectButton"; - this.ConnectButton.Size = new System.Drawing.Size(148, 25); - this.ConnectButton.TabIndex = 20; - this.ConnectButton.Text = "Connect"; - this.ConnectButton.UseVisualStyleBackColor = true; - this.ConnectButton.Click += new System.EventHandler(this.ConnectButton_Click); - // - // EnabledCheckBox - // - this.EnabledCheckBox.AutoSize = true; - this.EnabledCheckBox.ForeColor = System.Drawing.Color.White; - this.EnabledCheckBox.Location = new System.Drawing.Point(396, 149); - this.EnabledCheckBox.Name = "EnabledCheckBox"; - this.EnabledCheckBox.Size = new System.Drawing.Size(65, 17); - this.EnabledCheckBox.TabIndex = 21; - this.EnabledCheckBox.Text = "Enabled"; - this.EnabledCheckBox.UseVisualStyleBackColor = true; - this.EnabledCheckBox.CheckedChanged += new System.EventHandler(this.EnabledCheckBox_CheckedChanged); - // - // InstanceSelector - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.ClientSize = new System.Drawing.Size(518, 261); - this.Controls.Add(this.EnabledCheckBox); - this.Controls.Add(this.ConnectButton); - this.Controls.Add(this.RefreshButton); - this.Controls.Add(this.DetachInstanceButton); - this.Controls.Add(this.RenameInstanceButton); - this.Controls.Add(this.ImportInstanceButton); - this.Controls.Add(this.CreateInstanceButton); - this.Controls.Add(this.InstanceListBox); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.Name = "InstanceSelector"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "Server Instances"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.ListBox InstanceListBox; - private System.Windows.Forms.Button CreateInstanceButton; - private System.Windows.Forms.Button ImportInstanceButton; - private System.Windows.Forms.Button RenameInstanceButton; - private System.Windows.Forms.Button DetachInstanceButton; - private System.Windows.Forms.Button RefreshButton; - private System.Windows.Forms.Button ConnectButton; - private System.Windows.Forms.CheckBox EnabledCheckBox; - } -} \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/InstanceSelector.cs b/legacy/V3/TGS.ControlPanel/InstanceSelector.cs deleted file mode 100644 index 6794995db2..0000000000 --- a/legacy/V3/TGS.ControlPanel/InstanceSelector.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows.Forms; -using TGS.Interface; - -namespace TGS.ControlPanel -{ - /// - /// Form used for managing s - /// - sealed partial class InstanceSelector : CountedForm - { - /// - /// The we build instance connections from - /// - readonly IServer server; - /// - /// Used for modifying without invoking its side effects - /// - bool UpdatingEnabledCheckbox = false; - - /// - /// Construct an - /// - /// The value of - public InstanceSelector(IServer _server) - { - InitializeComponent(); - InstanceListBox.MouseDoubleClick += InstanceListBox_MouseDoubleClick; - server = _server; - RefreshInstances(); - } - - /// - /// Connects to a if it is double clicked in - /// - /// The sender of the event - /// The - void InstanceListBox_MouseDoubleClick(object sender, MouseEventArgs e) - { - TryConnectToIndexInstance(InstanceListBox.IndexFromPoint(e.Location)); - } - - /// - /// Returns the associated with 's current selected index - /// - /// The associated with 's current selected index if it exists, otherwise - InstanceMetadata GetSelectedInstanceMetadata() - { - var index = (IInstance)InstanceListBox.SelectedItem; - return index?.Metadata; - } - - /// - /// Loads the using - /// - void RefreshInstances() - { - InstanceListBox.Items.Clear(); - foreach(var I in server.Instances) - InstanceListBox.Items.Add(I); - var HasServerAdmin = server.InstanceManager != null; - CreateInstanceButton.Enabled = HasServerAdmin; - ImportInstanceButton.Enabled = HasServerAdmin; - RenameInstanceButton.Enabled = HasServerAdmin; - DetachInstanceButton.Enabled = HasServerAdmin; - EnabledCheckBox.Enabled = HasServerAdmin; - if(InstanceListBox.Items.Count > 0) - InstanceListBox.SelectedIndex = 0; - } - - /// - /// Tries to start a for a given - /// - /// The of to connect to - void TryConnectToIndexInstance(int index) - { - if (index == ListBox.NoMatches) - return; - var instance = (IInstance)InstanceListBox.Items[index]; - if (ControlPanel.InstancesInUse.TryGetValue(instance.Metadata.Name, out ControlPanel activeCP)) - { - activeCP.BringToFront(); - return; - } - - new ControlPanel(server, instance).Show(); - } - - /// - /// Prompts the user for parameters to - /// - /// The sender of the event - /// The - async void DetachInstanceButton_Click(object sender, EventArgs e) - { - var imd = GetSelectedInstanceMetadata(); - if (imd == null) - return; - if (MessageBox.Show(String.Format("This will dissociate the server instance at \"{0}\"! Are you sure?", imd.Path), "Instance Detach", MessageBoxButtons.YesNo) != DialogResult.Yes) - return; - string res = null; - await WrapServerOp(() => res = server.InstanceManager.DetachInstance(imd.Name)); - if (res != null) - MessageBox.Show(res); - RefreshInstances(); - } - - /// - /// Calls - /// - /// The sender of the event - /// The - void RefreshButton_Click(object sender, EventArgs e) - { - RefreshInstances(); - } - - /// - /// Prompts the user for parameters to - /// - /// The sender of the event - /// The - async void RenameInstanceButton_Click(object sender, EventArgs e) - { - var imd = GetSelectedInstanceMetadata(); - if (imd == null) - return; - var new_name = Program.TextPrompt("Instance Rename", "Enter a new name for the instance:"); - if (new_name == null) - return; - if (imd.Enabled && MessageBox.Show(String.Format("This will temporarily offline the server instance! Are you sure?", imd.Path), "Instance Restart", MessageBoxButtons.YesNo) != DialogResult.Yes) - return; - string res = null; - await WrapServerOp(() => res = server.InstanceManager.RenameInstance(imd.Name, new_name)); - if (res != null) - MessageBox.Show(res); - RefreshInstances(); - } - - /// - /// Prompts the user for parameters to - /// - /// The sender of the event - /// The - async void ImportInstanceButton_Click(object sender, EventArgs e) - { - var instance_path = Program.TextPrompt("Instance Import", "Enter the full path to the instance:"); - if (instance_path == null) - return; - string res = null; - await WrapServerOp(() => res = server.InstanceManager.ImportInstance(instance_path)); - if (res != null) - MessageBox.Show(res); - RefreshInstances(); - } - - /// - /// Prompts the user for parameters to - /// - /// The sender of the event - /// The - async void CreateInstanceButton_Click(object sender, EventArgs e) - { - var instance_name = Program.TextPrompt("Instance Creation", "Enter the name of the instance:"); - if (instance_name == null) - return; - var instance_path = Program.TextPrompt("Instance Creation", "Enter the full path to the instance:"); - if (instance_path == null) - return; - string res = null; - await WrapServerOp(() => res = server.InstanceManager.CreateInstance(instance_name, instance_path)); - if (res != null) - MessageBox.Show(res); - RefreshInstances(); - } - - /// - /// Attempts to connect the user to an based on the of - /// - /// The sender of the event - /// The - void ConnectButton_Click(object sender, EventArgs e) - { - TryConnectToIndexInstance(InstanceListBox.SelectedIndex); - } - - /// - /// Prompts the user if they want to call to either online or offline an based on its current state - /// - /// The sender of the event - /// The - async void EnabledCheckBox_CheckedChanged(object sender, EventArgs e) - { - if (UpdatingEnabledCheckbox) - return; - var enabling = EnabledCheckBox.Checked; - try - { - if (MessageBox.Show(String.Format("Are you sure you want to {0} this instance?", enabling ? "online" : "offline"), "Instance Status Change", MessageBoxButtons.YesNo) != DialogResult.Yes) - return; - string res = null; - await WrapServerOp(() => res = server.InstanceManager.SetInstanceEnabled(GetSelectedInstanceMetadata().Name, enabling)); - if (res != null) - MessageBox.Show(res); - } - finally - { - RefreshInstances(); - } - } - - /// - /// Update based on the selected 's property - /// - /// The sender of the event - /// The - void InstanceListBox_SelectedIndexChanged(object sender, EventArgs e) - { - if (InstanceListBox.SelectedIndex != ListBox.NoMatches) - { - UpdatingEnabledCheckbox = true; - EnabledCheckBox.Checked = GetSelectedInstanceMetadata().Enabled; - UpdatingEnabledCheckbox = false; - } - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/InstanceSelector.resx b/legacy/V3/TGS.ControlPanel/InstanceSelector.resx deleted file mode 100644 index a83f34c04b..0000000000 --- a/legacy/V3/TGS.ControlPanel/InstanceSelector.resx +++ /dev/null @@ -1,2179 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAgIAAAAQAgAKgQAABGDgAAMDAAAAEA - IACoJQAA7h4AAKCgAAABACAAqJwBAJZEAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAA - AAAAAAAAAAAAAAAAAABeTTgAWkgyA1pIMilOOyMoRjMaRz4qEMM+KhDESDUcSU07IyhaSDIpWUgxA15N - NwAAAAAAAAAAAAAAAADSzsgAPyoQAEk2HTk9KQ7ZOSUK5DciB+43Igf/OCQJ/zkkCe46JQrkPSgO2UYz - Gjk8JwwA0s3HAAAAAABVQywAbV1JAk47IytALBK6XEo0/2xcSP9lVUD/mo+C/5CEdf9jUz7/ZFQ//2BP - Ov9LOCC6SzgfK2hYRAJSPygA////AUg1HHA7JgzkOSQJ/29gTP9wYU7/6+nm/+rn5f9rXEj/U0Ap/1NB - Kv9YRzD/Z1ZC/zsmC+RINBtw////AYFzYgU/KhCxOiUK/4V4aP+hl4r/ZFQ///b19P/a1tH/c2RR/87J - w//X083/Y1M+/3BhTv8+Kg//PyoQsYFzYgVYRi8cPSkOz0MvFf/a1tH/vreu/2RTPv/08/H//Pz7//Py - 8P/+/f3/+vn5/3JjUP+Hemr/RjMa/z0pDs9XRS8cQi4VnDciB/xsXEn/9PPx/7y1rP9kUz7/9PLx//// - //////////////f29f9qW0f/oJaJ/3BhTf83Igf8Qi4VnD8qEN45JAn/saie//////+7tKv/ZFM+//Ty - 8f//////7u3q/5SJev95a1n/Y1M9/9rX0v+zq6D/OSQJ/z8qEN5EMBeFNiEF/oV4aP//////vLSr/2RT - Pv/08vH//////+7t6v+UiXr/eWtZ/2NTPv/X0s3/h3pq/zYhBf5EMBeFRzMaZTYhBfuAc2L//////7y0 - q/9kUz7/9PLx//////////////////f29f9qWkX/rqaa/4R3Zv82IQX7RzMaZUUxGIc3Igf/X085/97b - 1/+9tqz/ZFM+//Tz8f/8/Pv/8/Lw//79/f/6+fn/cWJP/5KHeP9kUz7/NyIH/0UxGIdMOSE0PikPvzkk - Cf+Lf2//in5u/2JSPf/29fT/2tbR/3JkUf/PycP/19PN/2NTPv9oWEP/OycM/z0pD79LOCA0RDEYAE89 - JSU/KxHWcGBN/1NAKf9wYU3/9vX0/+rn5P9pWkX/VEIq/1NBKv9YRzD/ZlZB/zwnDdZPPSUlRTEYAGdY - QwAAAAAAQCwSkkw5If9cSjT/bF1J/6ujl/+lm4//j4N0/4R3Z/9lVUD/YE86/0IuFP8/KxGSAAAAAGdY - QwAAAAAAQy8WAFA+JiFDLxZmPCcMnzciBvM5JAn/NyIH/zgjCP87Jgz/OCMI8zwnDZ9DLxVmTzwlIUEs - EwAAAAAAAAAAAAAAAAAAAAAAQi4VAEYyGQc+KQ+GPSgO6EEtE61CLhStPSkP6D0pDoZEMBcHQS0TAAAA - AAAAAAAAAAAAAOAHAADgBwAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB - AADAAwAAwAMAAPAPAAAoAAAAGAAAADAAAAABACAAAAAAAAAJAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wD///8A////AP///wBVQy0AemxaA047I0NCLhTGQS0TxlE+J0aFd2cEYlI9AP// - /wD///8A////AP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF5NNwBhUDsMTToifk47 - I50+KQ+BQy8WmzwnDeg4Iwj/OCMI/z0oDupDLxacPikPgU47I51NOiF+YFA6DF1NNwAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAj4R1AB8HAABLOCBgOiUK+DYhBf82IQX/OCMI/zciB/82IQX/NiEF/zUg - Bf82IQX/NiEF/zYhBf86JQr4SzgfYB8HAACOgnMAAAAAAAAAAAAAAAAAAAAAAAAAAACMgXIACAAAAGBP - OS0/KxHVRzMa/2BPOf9eTTf/QSwT/0UyGf+Lf2//joJy/15NN/9fTjn/X085/11MNv9KNx//PysR1V9O - OC0GAAAAjIFyAAAAAAAAAAAAnpWIANrY0wFaSTIhRTIZdUAsEtc2IQb/d2lW/35xX/+Lf2//vLWs/8/K - xP/7+vr/wLqx/3NkUf9qWkb/alpG/25fS/+GeWj/ZFM+/z8qENdFMRh1WEYvIdTOyQGZjX8AbF1KAG1e - ShxFMhjMOSQJ/jciB/8zHgL/dmhV/1E/KP9oWET/+Pf2///////h3tr/UD4m/zsnDP9EMBb/QzAW/zsm - C/9BLRT/hnlp/0EtFP84Iwj+RTEYzG1dShxsXUkATDkgAE06Iig7JgzoNyIH/z4pD/9wYU3/ubKo/1RB - Kv9qWkb/+Pf3///////Hwbn/OyYL/4p+bv/c2NT/3NjT/6iek/87Jwz/e21b/1E+J/82IQb/OyYM6E06 - IihMOSAAWUcwAF1MNjs9KQ/0NSAE/2pbRv/z8vD/7evo/1JAKf9qWkb/+Pf3///////p5+T/tq6l/9vX - 0v///////////9XRy/9BLRP/in1t/25fS/81HwT/PSkP9F5NNztZRzEAAAAAAEo3H3A6JQr9NB8D/3xu - XP//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XRy/9BLBP/kIR1/35w - Xv80HwP/OiUK/Uo3HnAAAAAAUkApRj4pD+M3Igf/OiUK/6iekv//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/k4d5/6qhlf86JQr/NyIH/z4pD+NSPyhGQy8V1jgj - CP82IQb/emxa//Tz8f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yj - mP83Igf/mI6A//b29P97bVv/NiEG/zgjCP9DLxXWRTEYxDgjCP82IQb/m5CD////////////7Oro/1JA - Kf9qWkb/+Pf3/////////////////6ackP9XRS7/WEYw/0IuFf91Z1T/4d7Z//////+bkIP/NiEG/zgj - CP9FMRjEUT4nXTsmDPU2IAX/XUw2/+3r6f//////7Oro/1JAKf9qWkb/+Pf3/////////////////6ac - kP9XRS7/WEYv/0IuFP92Z1T/4t/b/+7s6v9dTDb/NiAF/zsmDPVRPiddYlE7Gz8qENQ3Igb/SDUc/93a - 1f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yjmP83Igf/mo+B/9/c - 2P9INRz/NyIG/z8qENRhUTwbX044Jj8qEOA2IAX/WUcx/+3r6f//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/kod4/+/t6/9ZRzD/NiAF/z8qEOBfTjgmVEIqRjwn - DfI3IQb/TDkg/8bAuf//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XR - y/9BLBP/kYV2/8nDvP9MOSH/NiEG/zwnDfJUQitGVUMsQj4pD+Q4Iwj/NiEG/047I//PysT/7uzq/1JA - Kf9qWkb/+Pf3///////p5+T/tq6l/9vX0v///////////9XRy/9CLhT/fG5c/1tKNP82IQX/OCMI/z4p - D+RVQyxCwry1Ak07I0Y/KhDUNyIH/0s4IP+akIL/jIBw/0UxGP9rW0f/+Pf3///////Fv7j/OiYL/4p+ - bv/c2NT/3NjT/6iek/87Jwz/eWpY/006Iv83Igf/PioQ1E06Ika8ta0CY1M+AEIvFQBYRjA1PCcN3lA9 - Jf96a1n/NyIH/zQeA/9qW0b/+Pf2///////e29b/TToi/zsmC/9DLxb/QzAW/zsmC/9BLRT/hnlp/0Et - FP89KA7eV0YvNUMwFgBhUDwAAAAAAG5fSwD5+PcBQCwSm0QwF/+FeGj/gnVk/3lrWf+Vinz/9fPy//// - ///39vX/ubGo/4F0Y/9yYk//alpG/25fS/+GeWj/Y1M9/zciB/9BLhSb+fr5AW5gTAAAAAAAAAAAAAAA - AABBLRMASjcfVjsmDPs7Jgv/Qy8W/047I/9aSDL/hHdm/6CWif9xYk7/cWJP/52Thv+DdWX/Xk04/11M - Nv9KNx//OCMH/zsmDPtKNx9WQS0TAAAAAAAAAAAAAAAAAAAAAABcSzUAY1I9E006ImxFMRieQCsSxzgj - CPU2IAX/NiEG/zYhBv81IAT/NSAE/zYhBv82IQb/NSAF/zciB/U+KhDHQy8Wnko3H2xiUTwTXEo0AAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGXiwDJxL0BalpGDT4qEHM5JQrzOCMI/zgjCP86Jgv7OyYL+zgj - CP84Iwj/OSQK8z4pD3NnV0MNt66lAZKGeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzggAFNBKgVBLRSBPysR60QwFrBNOyNpTzwlaUYyGbBALBLrQS0TgVA+JgVJNh0AAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA/4H/APgAHwD4AB8A8AAPAIAAAQCAAAEAgAABAIAAAQCAAAEAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAADAMAAAwDgAAcA4AAHAPgAHwD+AH8AKAAAACAA - AABAAAAAAQAgAAAAAAAAEAAAEwsAABMLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHlrWQCGeWkEVkQtRkUyGclEMBfJU0EpR52UhwZ7blwAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAalpHAAAAAABlVUAmcGBNQG5fTDE8Jw0dXEs1L0g1HIw9KQ7qOCMI/zgjCP8+KQ/sTToikltK - My88Jw0dbl9MMXBgTUBlVUAmAAAAAGdXQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABaSDIAY1I9IEQwF8pBLRP5PysR7TkkCeA+KQ/rOSUK/jgjCP84Iwj/OCMI/zgj - CP85JAr+PSkP6zkkCeA/KxHtQS0T+UQwF8piUTwgWUgxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAeGpXALy2rQJKNx+MOSQK/zchBv83Igb/NyIH/zgiB/84Iwj/NyIH/zUg - Bf81IAX/NyIH/zciB/83Igf/NyIH/zciBv83Igf/OSQK/0o3Hoy6s6oCdmhWAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAANnUzwA0HwMAXk44Rz4pD+05JQr/RTEY/0g1HP9GMhn/OSQJ/zUf - BP9AKxH/e21b/3xuXP9HMxr/RjIZ/0g0HP9INBv/RzQb/0EtFP83Igf/PSgO7V5NN0czHgIA2dTPAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ2ThgAWAAAAYlE8ElRCK2BFMRfVNiEG/1xLNf+pn5T/lIl7/6ee - kv+DdmX/bl9L/763rv/5+Pf/8vHv/66mm/+Wi33/lop8/5aKfP+Wi33/mo+C/4l9bf9HNBv/Qy8W1VRC - K2BhUDsSJxAAAI+CcwAAAAAAAAAAAAAAAAByY1AAg3ZlFVE/J2lALBLFOyYM+jgjCP81IAT/a1tH/4Z5 - af83Igf/e21b//X08//39vX///////v6+f+aj4L/STYd/z4pD/89KA7/PSgO/z0pDv9DLxb/dGZT/5WK - e/8+KQ//OyYL+j8rEcVQPSVpgHJhFW5eSwAAAAAAAAAAAEw6IgBgTzpiPioQ/jgjCP84Iwj/OCMI/zMe - Av9pWUT/g3Zl/zEcAP93aVb//Pv7////////////1tLN/0UyGf83Igf/Qi4U/0MvFv9DLxb/QCwS/zci - B/83IQb/kIR1/15NN/82IAX/OCMI/z4qEP5gTzliTDkhAAAAAAAAAAAAPCgNAEMvFWk5JAn/OCMI/zgj - CP86JQr/V0Uv/66lmv+Ie2v/MRwA/3dpVv/8+/v///////////+5sqn/NSAF/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cWJP/zUgBP84Iwj/OSQJ/0MvFmk8KA0AAAAAAAAAAAAAAAAAU0AphDkk - Cf84Iwj/OCIH/4x/cP/q5+T//////4p+bv8xHAD/d2lW//z7+////////////8fBuv9oWUT/fW9d/+7s - 6v////////////////+Wi3z/Mh0B/3VmU/+YjX//NyIH/zgjCP85JAn/U0EphAAAAAAAAAAAemlYAAAA - AABPPSWYOSQJ/zgjCP87Jgv/vbat////////////in1u/zEcAP93aVb//Pv7/////////////Pz8//j3 - 9v/5+Pj//v7+/////////////////5iMf/8yHQH/d2hW/7ixp/87Jgv/OCMI/zkkCf9PPSWYAAAAAHRm - UgBcSzUAZVQ/J0EtE9M4Iwj/OCMI/z0pDv/JxL3///////////+KfW7/MRwA/3dpVv/8+/v///////// - ////////////////////////////////////////mIx+/zIdAf93aFb/xb+3/z0pD/84Iwj/OCMI/0Et - E9NjUz4nW0kzAHlrWBBJNh2oOSQJ/zgjCP82IQb/U0Ap/+He2v///////////4p9bv8xHAD/d2lW//z7 - +/////////////////////////////////////////////////+YjH7/Mh0B/3VnVP/d2dX/U0Ep/zYh - Bv84Iwj/OSQJ/0g1HKh3aVcQTjsjjDsmC/k4Iwj/NyIH/0AsEv+zq6H/////////////////in1u/zEc - AP93aVb//Pv7/////////////////////////////////////////////////4t/b/8yHAD/c2RR//v7 - +v+0raP/QCwT/zciB/84Iwj/OyYL+U47I4xFMRjoOCMH/zgjCP82IAX/hHdm//r6+f////////////// - //+KfW7/MRwA/3dpVv/8+/v///////////////////////f29f/Szsj/zcjC/87Jw/+wqJ7/Tzwk/zUf - BP+MgHD///7+//v6+v+Ed2f/NiAF/zgjCP84Iwf/RTEY6E48JJs5JAn/OCMI/zUgBf97bVv/+vn5//// - /////////////4p9bv8xHAD/d2lW//z7+///////////////////////4d7Z/1A+Jv89KQ//PioQ/zkk - Cv89KQ//nJGE/+zq5///////+vn5/3ttW/81IAX/OCMI/zkkCf9OPCSbYlI9O0AsEuk4Iwf/NyIH/0Yy - Gf/Tzsj/////////////////in1u/zEcAP93aVb//Pv7///////////////////////h3tn/UD4m/z0p - D/8+KhD/OSQJ/z0pD/+ckYT/7Oro///////Tzsj/RjIZ/zciB/84Iwf/QCwS6WJSPTuooJQFRTEYoDgj - CP84Iwj/NiEG/6KYi/////////////////+KfW7/MRwA/3dpVv/8+/v///////////////////////f2 - 9f/Szsj/zcjC/87Jw/+wqJ3/Tzwk/zUfBP+MgHD//////6KYi/82IQX/OCMI/zgjCP9FMRigqJ+TBQAA - AABOOyOLOSQJ/zgjCP84Iwj/sKid/////////////////4p9bv8xHAD/d2lW//z7+/////////////// - //////////////////////////////////+Lf2//MhwA/3NkUf/+/v7/sKid/zgjCP84Iwj/OSQJ/048 - I4sAAAAAycO7BUk1Ha04Iwj/OCIH/0ArEf/MxsD/////////////////in1u/zEcAP93aVb//Pv7//// - /////////////////////////////////////////////5iMf/8yHQH/dGVS//79/f/Mx8D/QCsR/zgi - B/84Iwj/STYdrcjEvAV8b10UQzAWzDgjCP84Iwf/PikP/62kmf/9/Pz///////////+KfW7/MRwA/3dp - Vv/8+/v/////////////////////////////////////////////////mIx+/zIdAf90ZVL/+fj3/66m - m/8+KQ//OCMH/zgjCP9EMBbMfW9eFGVVQCZBLBPhNyIH/zgjCP83Igf/Qy8V/5yShf/18/L//////4p9 - bv8xHAD/d2lW//z7+/////////////z8/P/49/b/+fj4//7+/v////////////////+YjH//Mh0B/3Rl - Uv+nnZH/Qy8W/zciB/84Iwj/NyIH/0EsE+FlVUAmhHZmDE48JIE8Jw3zOCMI/zgjCP81IAX/VkQt/9bS - zf/+/v7/in1t/zEcAP93aVb//Pv7////////////x8G6/2hZRP99b13/7uzq/////////////////5aL - fP8yHQH/cWJO/3RlUv80HwP/OCMI/zgjCP88Jw3zTTsjgYF0YwxjUj0AfG5dBE88JV4/KxHkOCMI/zUg - BP91Z1T/kIV2/3NlUv9XRS//Mx4C/3dpVv/8+/v///////////+3sKb/NSAE/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cmNP/zUgBP84Iwj/PyoQ5E48JF58blwEY1I9AAAAAAByZVEA////AF5N - OEk+KhDmNR8E/3ZnVP9tXUn/LxkA/zUfBP80HgP/d2hW//z7+////////////9LNx/9CLhX/OCMI/0Iu - FP9DLxb/Qy8W/0AsEv83Igf/NyEG/5CEdf9eTTf/NiAF/z4qEOZdTTdJ////AHBhTgAAAAAAAAAAAAAA - AACBc2MAvreuA0UxGKM1IAX/cWJP/5qQgv9pWUX/Wkgy/0g1HP9/cWD//Pv7////////////+fj4/5SJ - ev9GMhn/OyYM/zwnDf89KA7/PSkO/0MvFv90ZlP/lYp8/z4qD/84Iwj/RTEYo764sAOBdGQAAAAAAAAA - AAAAAAAAAAAAAAAAAAA6JQoASzggXDkkCfxDLxb/bF1J/4ByYf+NgXL/lop8/62kmf/g3dn///////j3 - 9v/f29f/2NTP/8/KxP/CvLT/pZyQ/5WKfP+Wi33/mo+C/4l9bf9HNBv/NyIG/zolCvxLOCBcOiUKAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBdTDYkQCwS4jciB/81HwT/NR8E/zciB/88Jw3/QS0T/1pI - Mv+UiHr/cGBN/0s4H/9KNx//cGBN/5aLff9fTzn/RzMa/0c0G/9BLRT/NyIH/zciBv84Iwj/QCwS4l1M - NiRdTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi39wAI2AcgVWRC1lSDUcqEMvFs0/KxHqOiUK/Dgj - CP84Igf/NyEG/zUgBf81IAT/NyIH/zciB/81IAX/NSAF/zYhBv83Igf/NyIH/zolCvw+KhDqQS0UzUYy - GahUQiplkIR1BYyAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJuRhACzq6IDeGpYEWNT - PipCLhV5OiUK8jgjCP84Iwj/OCMI/zgjCP84Igf/OCIH/zgjCP84Iwj/OCMI/zgjCP85JAryQi4UeWFQ - OypvX0wRpJqOA4x/cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzgfAFVDKwRCLhRtOiYL8jgjCP84Iwj/OiUL+0EsE+dBLRPnOyYL+zgjCP84Iwj/OiUL8kEt - E21RPicESTUdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAU0EqAGBPOgRFMhl9QS0T7Ek2HblNOiJpZFQ/MGhYQzBRPidpSzkguUIu - FOxEMRd9XEw2BFE/KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+B///wAA//4A - AH/8AAA//AAAP/AAAA/AAAADwAAAA8AAAAPAAAADwAAAA4AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACAAAABAAAAAAAAAAAAAAAAAAAAAIAAAAHgAAAH4AAAB/AAAA/wAAAP8AAAD/wAAD//gAH//8AD/ygA - AAAwAAAAYAAAAAEAIAAAAAAAACQAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGxc - SACShXcEaVlFT0o3HstINBzLZFM+T4R3ZwRkUz4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACXjH4AAAAAAGdXQyNRPiaTQCsR6zkkCf85JAn/PyoQ6048JJR+cF8v////AIl8bAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1c/OAGtcSAB0ZVMib2BMU4F0 - Y1OMgHBJSTYdIT4pDx1rW0cmXUw2dEIuFNY6JQr+OCMH/zgjCP84Iwj/OCMI/zolC/9HMxrgW0kzdWtb - RyY+KQ8dSTYdIYyAcEmBdGNTb2BMU3RlUyJrXEgAysjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfW9eAIR3 - Zw1MOSGsQS0T/0UxGP5FMRj4OSQK4zolC+FBLRPmPSkO/DgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP83Igf/PSgO/EEtE+Y6JQvhOSQK40UxGPhFMRj+QS0T/0s5IKyEd2YNfW9dAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACgl4sAAAAAAGFQOmw9KA76NyIH/zciB/83Igf/OCMI/zgjCP84Iwf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjB/84Iwj/OCMI/zciB/83Igf/NyIH/z0oDvpgTzpsAAAAAKKY - jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABXRS4Aa1tHJEQxF9U4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zYhBf82IQX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9EMBfValpGJFZELQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCWiQDEvbUHVEIrmjolCv84Iwj/NiEF/zUgBP81IAT/NSAE/zUg - BP83Igf/OCMI/zgjCP82IQb/OSQJ/2BPOf9gUDr/OSQJ/zUgBP81IAT/NSAE/zUgBP81IAT/NSAE/zUg - BP81IAT/NiEG/zgjCP86JQr/VEEqmsG6sweek4cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl41+AODd2ARtXUleQi4U9DciB/85JAn/YE86/31v - Xf98b13/fG9d/3lrWf9KNx//NB8D/zYgBf9PPSX/n5WI/+zq5//t6+j/oZeK/21dSv94aVf/fG9d/3xv - Xf98b13/fG9d/3ttXP9wYU7/VUMs/zomC/82IQb/Qi4U9GxdSV7h3dkEl41+AAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AGtaRgCNgHAFY1I9OlNBKp9EMBbvOCMI/zgj - CP8+Kg//wbuy/7Wto/+il4v/oZeL/8nEvf+so5j/alpG/4l9bf/b19L//v79////////////7Orn/763 - rv+onpP/opiM/6OYjP+jmIz/opiM/6Sajv+wp53/w721/6ifk/9OOyP/NiEG/0QwFu9TQiqfY1M+Oo2A - cQVrW0cA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wANAAAAqKCUDlxKND9JNh2fQCwS7Tkk - Cf83Igf/OCMI/zgjCP8/KxH/wLqx/11MNv81IAT/NR8E/4+DdP/+/v7/+Pf2//v7+/////////////// - ///Qy8T/Y1I9/z0pD/84Iwj/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TToh/5qPgv+2rqT/RzQb/zYh - Bv85JAn/QCwS7Uc0G59ZRzE/p52RDh4IAAD08e8AAAAAAAAAAAAAAAAAAAAAAJ+WiQCjmo0YX085sj8r - EfM4Iwj/OCMH/zgjCP84Iwj/OCMI/zgjCP8/KxH/wLqx/15NN/82IAX/NSAF/4+DdP////////////// - /////////////+zq5/9jUj3/NB8D/zgjCP84Iwj/OCIH/zgjB/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsm - C/+upZr/in5u/zYgBf84Iwj/OCMH/zgjCP8/KxHzXkw3sqGXihiflIcAAAAAAAAAAAAAAAAAAAAAAIF0 - YwB/cmEtSDUc7TchBv84Iwj/OCMI/zgjCP84Iwj/OCMI/zciB/89KQ7/wLmw/15NN/82IAX/NSAF/4+D - dP///////////////////////////8C6sf88Jw3/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9sXEj/tKyi/zwoDv84Iwj/OCMI/zgjCP83IQb/SDUc7YByYS2CdGQAAAAAAAAA - AAAAAAAAAAAAAFZFLgBWRC0tPysR7TgjCP84Iwj/OCMI/zgjCP83Igf/NSAE/zwoDf9rXEj/1NDK/11M - Nv82IAX/NSAF/4+DdP///////////////////////////6KYi/82IQb/NyIG/0s4IP+6s6n/y8W+/8rE - vP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYhBv9RPyj/vLSr/0g0HP83Igf/OCMI/zgjCP84Iwj/PysR7VZE - LS1XRS4AAAAAAAAAAAAAAAAAAAAAAEEtEwBINBsxOSUK7zgjCP84Iwj/OCMI/zgjB/9ALBL/cGFO/7ix - p//y8O7/8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////5aKfP8yHAH/Mx0C/089 - Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9KNx//vreu/1NBKv83IQb/OCMI/zgj - CP84Iwj/OSUK70c0GzFBLRMAAAAAAAAAAAAAAAAAAAAAAGVUPwB+cF5eQCwS/jgiB/84Iwj/OCMI/zYg - Bf+GeWj/9/b1////////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ix - p/93aFb/d2lX/4p+bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9INBv/1tHM/4p9 - bf81IAX/OCMI/zgjCP84Igf/QCwS/n5wXl5lVD8AAAAAAAAAAAAAAAAAAAAAAFpJMwBsXEhpPysR/zgj - CP84Iwj/OCMI/zYhBv+glYj/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////7+/v/+/f3//v79//7+/v////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/3NjT/6GYi/82IQb/OCMI/zgjCP84Iwf/PysR/2xdSWlbSTMAAAAAAAAAAAAAAAAAn5WIAAAA - AABPPSV9OiYL/zgjCP84Iwj/OCMI/zgjCP+tpZr/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/3NjT/6+nnP84Iwj/OCMI/zgjCP84Iwj/OiYL/089JX0AAAAAn5SIAAAA - AAD///8AY1M+AG5fS0BFMRjnNyIH/zgjCP84Iwj/OCMI/zkkCf+8taz/////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+/uLD/PCgN/zciBv9HNBv/3dnU/764r/85JAn/OCMI/zgjCP84Iwj/NyIH/0Ux - GOdsXUlAYlE8AP///wB7bVsAgHJhH0s4IMY4Iwj/OCMI/zgjCP84Iwj/NyIH/0MvFf/OycP///////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////+/ubD/PCgN/zciBv9HNBv/3NjT/9HMxf9DLxX/NyIH/zgj - CP84Iwj/OCMI/zgjCP9LOCDGf3JgH3ttWwCimYwNUkAomjolC/84Iwj/OCMI/zgjCP84Iwj/OCMI/5CE - df/6+vn/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////++t67/PCcN/zciBv9HNBv/2tbQ//38 - /P+RhXb/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQv/UT8omqCWiQ1ZSDJ6PioQ9jgjB/84Iwj/OCMI/zgj - CP82IAX/bl9L/+3r6f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+qoZX/OCMI/zci - B/9HNBv/2dXQ///////u7Or/cWJO/zYgBf84Iwj/OCMI/zgjCP84Iwf/PioP9llHMXpKNx/tNyIH/zgj - CP84Iwj/OCMI/zYhBv9UQiv/19LN////////////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9PO - yP9fTjj/NiEG/zUgBP9PPCT/4t/c////////////2NTP/1RCK/82IQb/OCMI/zgjCP84Iwj/NyIH/0s3 - H+1XRS+1OiUK/zgjCP84Iwj/OCMI/zUgBP9uX0z/9/b1////////////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP///////////////////////////////////////////+Th3v91Z1T/aFhE/2lZ - Rf9pWUX/ZlVB/0o3Hv83Igf/OyYL/0s4IP+flYj//Pv7////////////9/b1/25fTP81IAT/OCMI/zgj - CP84Iwj/OiUK/1dGL7VrW0hMQCsS7zgjB/84Iwj/OCMI/zciB/9FMRj/zsnC//////////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////////////////////9vX - 0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c1//8+/v/////////////////zsnC/0Ux - GP83Igf/OCMI/zgjCP84Iwf/QCsR72tbR0yglooMUj8ouTgjCP84Iwj/OCMI/zgjCP81IAX/iX1t//7+ - /f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - /////////////9vX0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c2P/8+/v///////// - ///+/f3/iX1t/zUgBf84Iwj/OCMI/zgjCP84Iwj/Uj8ouaGXiwwbAwAAZVVAYD0pD/o4Iwj/OCMI/zgj - CP82IQb/UD4m/+Dd2P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - /////////////////////////////+Th3v91Z1T/aFhE/2lZRf9pWUX/ZlVB/0k2Hv83Igf/OyYL/0s4 - IP+glon//Pv7///////g3Nj/UD4m/zYhBv84Iwj/OCMI/zgjCP89KQ/6ZVVAYBsDAABtXUoAcWNPE0Et - FMY4Iwj/OCMI/zgjCP84Iwf/PSkP/8S+tv//////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9LO - yP9eTjj/NiEG/zUgBP9PPCT/4+Dc///////Evrb/PSkP/zgjCP84Iwj/OCMI/zgjCP9BLRTGcWJQE21d - SgCwp5wAxb63C1I/KL83Igf/OCMI/zgjCP83Igf/RzMa/9nW0P//////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+qoZX/OCMI/zciB/9HNBv/2dXQ///////Z1dD/RjMa/zciB/84Iwj/OCMI/zci - B/9SQCi/xsG4C7CpngByYk8AdmdVFUUxGNU4Iwj/OCMI/zgjCP82IQb/WEYw/+vp5v////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////++t6//PCcN/zciBv9HNBv/2dXQ///////r6eb/WEYv/zYh - Bv84Iwj/OCMI/zgjCP9FMhjVdmhVFXFjUAB+cF4Af3FgNkUxGPA3Igf/OCMI/zgjCP81IAT/b2BN//n5 - +P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////+/ubD/PCgN/zciBv9HNBv/2dXQ//// - ///5+fj/b2BN/zUgBP84Iwj/OCMI/zciB/9FMRjwf3JgNn5wXwBKNh4AXUw2Sj0oDvk4Iwj/OCMI/zgj - CP82IAX/bF1J/+7s6v//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/2dXQ///////v7ev/bV1J/zYgBf84Iwj/OCMI/zgjCP89KA75XUw2Sko3HgA6JgsAY1M+ejwn - Df84Iwj/OCMI/zgjCP84Iwj/OiUL/3lqWP/j4N3/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/2tbR/+bk4P96bFr/OyYL/zgjCP84Iwj/OCMI/zgjCP88Jw3/ZFM+ejsm - CwAAAAAATz0ljDkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zYhBv9hUDr/zsnC//7+/v//////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////7+/v/+/f3//v79//7+/v////////////// - //////////////////+/uLD/PCgN/zciBv9JNR3/x8G5/21dSf82IAX/OCMI/zgjCP84Iwj/OCMI/zgj - CP85JAn/Tz0ljAAAAAAAAAAAcGFOREo2HtQ4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP83Igf/emxa//Dv - 7f//////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ixp/93aFb/d2lX/4p+ - bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9JNh3/urKo/047I/83IQb/OCMI/zgj - CP84Iwj/OCMI/zgjCP9JNh3Ubl9LRAAAAADU0MsAAAAAAGFRPCROOyO3OiYL/TgjB/84Iwj/OCMI/zci - B/9KNh7/x8G5/8zGv//X087/2tbR/1hHMP82IQX/NSAF/4+DdP///////////////////////////5SJ - e/8yHAH/Mx0C/089Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9LNx//u7Oq/088 - JP83Igb/OCMI/zgjCP84Iwf/OiYL/U47I7dhUTwkAAAAANTQywAAAAAAxL61AEUxGQCAcmEUSzggiz8r - Efo4Iwj/OCMI/zciBv9PPSX/u7Oq/048JP9EMRf/UD4m/z4qEP84Iwj/NSAF/4+DdP////////////// - /////////////52Thv82IQb/NyIG/0s4IP+6s6n/y8W+/8rEvP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYh - Bv9RPyj/vLWs/0g1HP83Igf/OCMI/zgjCP8/KxH6Sjcfi31vXRRDLhUAxL61AAAAAAAAAAAAAAAAAAAA - AACFeGgAnJOFCmRUP3w+KhDzOCMI/zciBv9PPCX/urKp/0k2Hf82IQb/NiEG/zgjCP84Iwj/NSAF/4+D - dP///////////////////////////7qyqf86JQr/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9rXEj/tK2i/zwoDv84Iwj/OCMI/z4qEPNjUz58mY+BCoJ2ZQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAjIBwAJyShAhJNh61OCMI/zciBv9PPSX/urKp/0c0G/80HgP/NSAF/zYh - Bv83Igf/NSAE/4+DdP///////////////////////////+Xi3/9ZSDH/NB8E/zgjCP84Iwj/OCIH/zgj - B/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsmC/+tpZr/in5u/zYgBf84Iwj/OCMI/0o3HrWckoUIjIBwAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJGEACgRAABUQituOyYL/zciB/9KNh7/x8G5/6GX - iv+BdGP/bV5K/1hHMP9KNx7/OycM/5CEdf/////////////////////////////////Fv7f/XUw2/zwo - Df83Igf/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TDoh/5qPgf+2rqT/RzQb/zciB/84Iwj/OyYL/1RC - K24oEQAAnJKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBiUjwtQCsR6Dci - B/86JQr/ZVVA/4p+bv+imIz/same/722rf+/uK//uLGn/9XRy//5+Pj///////////////////////z8 - +//9/fz/7evo/8bAuP+so5j/p52S/6Sajv+imIz/opiM/6Sajv+wp5z/w721/6ifk/9OOyP/NiEG/zgj - CP84Iwj/QCsR6GJSPC1dTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHhq - VwCNgnIIRTIYszgjCP84Iwj/NiAF/zUgBP82IQb/OyYM/0MwFv9QPSb/ZFQ//3FiT/9+cF//4d3Z//f2 - 9f/c2NP/saie/31vXf98b13/rqWa/9vY0//5+Pf/4+Hd/4x/cP97blz/fG9d/3ttXP9wYU7/VUMs/zom - C/83Igb/OCMI/zgjCP84Iwj/RTIZs46Dcwh4a1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAMG5sAAvGQAAXEo0ejsmDP83Igf/NyIH/zgjCP84Iwj/OCMI/zciB/83IQb/NSAF/zUg - BP82IQX/YE85/21eSv9JNh3/OSQJ/zUgBP81IAT/OCMI/0g0HP9rW0f/YE86/zYgBf81HwT/NSAE/zUg - BP81IAT/NiEG/zgjCP84Iwj/NyIH/zciB/87Jgz/XEs1ei4YAADEvrQAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYRjAAbl5LLFNBKrZEMRfaQi4U9DwnDfw5JQr/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/NiEF/zUgBf83Igf/OCMI/zgjCP84Iwj/OCMI/zciB/81IAX/NiEF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf87Jgz8QCwS9EIuFdpRPie2bl9MLFhHMQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARTEYALGpngp6bFoaa1xIO2JS - PWRRPyiMTjsjvzolC/A4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQrwTjsjv088JIxdSzVkZFM+O3FiTxqil4sKNCAGAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAPb19AD///8BnpOGCEIuFWY7JgzwOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsmC/BBLRRmnZOGCP///wH29fQAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkMrAG5dSQJGMxlmPCcN7zgjCP84Iwj/OCMI/zgj - CP84Iwf/OCMI/zsmC/87Jgv/OCMI/zciB/84Iwj/OCMI/zgjCP84Iwj/PCcM70QwF2ZmVkMCUT8oAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFlHMABxYU4CSTYdZT0o - Du84Igf/OCMJ/zwnDP1AKxHmSTYdvV1MNoleTTiJTDkhvUEtE+Y8KA39OCQJ/zciB/88KA3vSDUcZWlb - RgJUQywAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AABjUj0Af3FiA007InRFMhjvTjsjw11NN3xlVUAyi39vC////wH///8BlIl7C21dSTJjUz58UT8ow0cz - Gu9MOCB0eGpWA19ONwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///gf//8AAP//8A///wAA//AAAA//AAD/4AAAB/8AAP/gAAAH/wAA/8AAAAP/ - AAD/gAAAAf8AAP8AAAAA/wAA/AAAAAA/AADwAAAAAA8AAOAAAAAABwAA4AAAAAAHAADgAAAAAAcAAOAA - AAAABwAA4AAAAAAHAADgAAAAAAcAAOAAAAAABwAAwAAAAAADAACAAAAAAAEAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA - AAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAMAAAAAAAwAA4AAAAAAH - AADwAAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAA/gAAAAB/AAD+AAAAAH8AAP8A - AAAA/wAA//AAAA//AAD//AAAP/8AAP/+AAB//wAA//8AAP//AAAoAAAAoAAAAEABAAABACAAAAAAAACQ - AQATCwAAEwsAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AHNjUf9zY1H/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Am5CD/z4pD/84Iwj/OCMI/zsnDP+NgXL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/UD0m/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0w4IP+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBvXkv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2dX - RP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI2Bcv87Jgv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/4FzYv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Atq2j/0k1Hf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/6eek/////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AY1Q//zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/X085/9DMxv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCNgXL/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/gXNi/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/9DLxf/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9AKxL/m5CD//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ApJuQ/3ttW/+Ie2r/kIN0/5eMfv+il4z/q6OW/7Ssov+9tq3/yMK6/9DMxv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AysS+/1hHMP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9YRzD/ycS9/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDQzMb/yMK6/722rf+0rKL/q6OW/6KXjP+XjH7/kIN0/4h7 - av97bVv/pJuQ/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9CLhT/Sjcf/1NA - KP9fTzn/Xk02/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/X085/1NAKP9KNx//Qi4U/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zolCv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGJRPf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKmg - lP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBDLxf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wB6alj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP96bFr/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDHwrn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/8O9tf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AU0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5eMfv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKq - oP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/sqqg//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBINRz/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wCDd2X/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OSQJ/1dGLv/Iwrr/yMK6/1dGLv84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/gnRk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS/5uQg/////////////// - ////////p56T/0IuFP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/MxsD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AWEcw/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/emxa////////////////////////////////////////////iHtq/zkk - Cf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8An5aI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZW - Qv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/dGRT/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/0MzG//// - ///////////////////////////////////////////////////QzMb/Xk02/zgjCP84Iwj/Qi4U/1xL - Nf9pWUX/dGRT/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3ps - Wv96bFr/emxa/3psWv9uYEz/ZlVB/048JP86JQr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Av7ev/z8qEP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - ////////////////////////////////////////////////////////RjMZ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf+tpJj///////////////////////////////////////// - ///////////////////////////////////KxL7///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////8/Lxf+hl4n/Y1M+/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PyoQ/7+3r/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wC0rKL/V0Yu/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////////////////////////////////////////////////// - /////////////////////////////3xuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/4l+ - bv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////iX5u/zsnDP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/V0Yu/7Ss - ov////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9OPCT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP/JxL3///////// - //+Ie2r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZlZC//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////8O9 - tf+2raP/qJ2S/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KX - jP+il4z/opeM/6Sajv+yqZ//wLmx/87Iwv//////////////////////////////////////altG/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk/6eek/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Am5CD/0c0G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy///////////////////////Szsj/ycS9/722 - rf+2raP/wryz//////////////////////////////////////////////////////////////////// - /////////////////////////////7Cnnf92Z1X/RjMZ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/1VDLP+Qg3T///////////////////////////+ajoH/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RzQb/52RhP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCQg3T/QS0T/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////pp2R/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r//////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/5CEdf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AI2Bcv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsnDP+kmo7///////// - ////////lIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PCgN/4FzYv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AgXNi/zol - Cv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////VUMs/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////////9mVUH/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/3Nl - Uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMnEvf9sXEj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ZlZC/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/21dSf/KxL7/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Awry0/2NT - Pv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////pp2R/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv////////////////9/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1xLNf+2raP/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AubOq/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////0c0 - G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/dGZU/////////////////zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7mxqP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AKmglP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9eTTb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////9lVUD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/k4d3////////////m5CD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AjIBx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////PCgN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1VELf///////////9HM - x/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AHtuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OSQJ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9/cmD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBvXkv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////6adkf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/PCgN/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/YlI8/0MvF/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8S+t////////////1dGLv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/b15L/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AX085/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+PgnT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - ////////////////////////////////////////////////////////////////////////wbux/1E+ - J/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+onZL///////// - //9wYU3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/19POf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFA+Jv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/q6OW//////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////gnRk/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPib/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBCLhT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1pIMf+2raP///////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////3ZnVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8jCuv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv///////////5CEdf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/7ato////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWkX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////UT4n/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Bc2L///////// - //+YjYD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9wYU3/zsjC//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OiUK/3xuXf////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2hXQ/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////7KqoP86JQr/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDPycL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9oV0P/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/PycL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AwLmx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/1tKNf////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////9bSTT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/wLmx/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKpn/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////aVpF/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7KqoP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCimY3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/empY//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4d7a/////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+GeWj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/lIp7/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIV3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+XjH7///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////lot8/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4Z5aP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB1aFT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pp2R//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////6SbkP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AZ1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Ssov////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+yqqD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEYzGf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/CvLT///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////wry0/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AG9eS/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0czH//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/altG/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJuQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+bkIP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AMzGwP86JQr/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/8jC - uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBMOCD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9JNR3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBsXEj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/RTEY//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////QzAW/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/2lZRf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCXjH7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1RC - Kv////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////1NAKP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/mI2A/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wDGwLj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+elIb///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////+elIb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf/GwLj/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ARzQb/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zkkCf9sXEj///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////25g - TP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZlZC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9KNx////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////TToi/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9iUT3/////AP///wD///8A////AP///wD///8A////AP///wD///8Ak4d3/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jwz/zsjC//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3dl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////9POyP88KA3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5KH - eP////8A////AP///wD///8A////AP///wD///8Aw762/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/opmN//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////opeM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wLmx/////wD///8A////AP// - /wD///8A////AEYzGf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cWJP//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9iUjz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////92Z1X/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0UxGP////8A////AP///wD///8A////AGNUP/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////1E+J/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/YVA7/////wD///8A////AI2Bcv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OycM/9DM - xv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////////////////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+PgnT/////AMK8 - tP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////1pIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3tuXP///////////////////////////////////////////////////////////6ad - kf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7+3r/9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////4l8 - bP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+QhHX///////// - ////////////////////////////////////////////////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9TQCj/lYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1A9Jv////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3tuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wryz//////////////////////////////////// - //////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/l4x+//// - /wA7Jwz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //+5s6r/hnlo/0ArEv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/UT4n//////////////////////////////////////////////////////////////////// - /////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/////wD///8Aemxa/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/w721//////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/8nEvf////////////// - /////////////////////////////////////////////////////////////8K8tP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3ttW/////8A////AM7Iwv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29eS/////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QCsS/7u1rP////////////////////////////////////////////// - //////////////////////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Py8X/////AP// - /wD///8AYU86/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9HNBv/hnlo//// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/////AP///wD///8A////ALSsov84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4p+cP////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/XEs1/722rf////////////////////////////////////////////// - ////////////////////////////////////////////////////////iX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/tKyi/////wD///8A////AP///wD///8ASTYe/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - /////////////////////////////0EtE/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////wD///8A////AP// - /wD///8A////AJiNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3///////////////////////// - /////////////////////////////////////////////////////////////////////////////6Sb - kP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5qOgf////8A////AP///wD///8A////AP///wD///8AOycM/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9QPib///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/fG5d//////////////////////////////////////////////////// - //////////////////////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwo - Df////8A////AP///wD///8A////AP///wD///8A////AHxuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/8G7 - sf////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - ///////////////////AubH/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3/////AP///wD///8A////AP// - /wD///8A////AP///wDSzsj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9sXEj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/vbat//////////////////// - ////////////////////////////////////////////////////////////////////////aVpF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/087I/////wD///8A////AP///wD///8A////AP///wD///8A////AGJS - PP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/4d7a/////////////////////////////// - /////////////////////////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wC2r6T/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+He2v///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/722rf////////////////////////////////////////////// - /////////////4Z6af84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7avpP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEo3H/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP87Jwz/ysS+//////////////////////////////////////////////////////9AKxL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0k2Hv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCbkIP/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/4J0ZP8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1NAKP////////////// - //////////////////////////////////+il4z/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+bkIP/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/bmBM//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3RmVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wry0//////////////////////////////////// - ////////bmBM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP88KA3/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/5GGdv///////////////////////////////////////////4yAcf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/f3Fh/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+vp5z///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP97blz///////// - //////////////////////////////////+tpJj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/0MzG//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubOq/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////zsjC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDMxsD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/zcjB/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Aq6GV/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0w4IP////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////Y1Q//zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////Sjcf/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6ujlv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIl8bP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Jfm7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBoV0P/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/joRz//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////+NgXL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ARzQb/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Cnnf////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////r6ec/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/RzMf///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMrEvv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////OSQJ/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8rGv/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/TToi//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////0w4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+poJT/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiHtq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29e - S/////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - //////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/h3tr/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGZVQf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Qg3T///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////joRz/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBGMxn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////6idkv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////722rf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AycS9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5SKe/////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////////////////////6KXjP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP/KxL7/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cGFN//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////3VoVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qaCU//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCGemn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9UQir///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////1hH - MP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4h7av////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0Iu - FP/CvLP///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////x8C4/0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9mVUH/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEUx - GP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/6KXjP////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////p56T/zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QzAW//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fnFf//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////q6OW/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP// - /wD///8A////AP///wDIwrr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9fTjf///////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/yMK6/////wD///8A////AP///wD///8A////AP///wD///8A////AHxu - Xf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2 - Hv/Py8X//////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fG5d//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Ap56T/zolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////Z1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDEvrf/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9RPif/hnlo/3xuXf////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2VVQP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9DLxf/xL63/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBVRC3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9QPSb///////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9nV0T/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9VRC3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AHNj - Uf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9zY1H/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Alot8/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////8G7sf+7s6v///////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2dXRP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9QPSb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4FzYv///////////5iNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Wi3z/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wC7s6v/PyoQ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/RjMZ/2JSPP98bl3/mI2A/7Ssov/OyML//////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWUX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///JxL3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //+QhHX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/z4pD/+2raP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBPPCX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////dGZU/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/088 - Jf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AGpb - Rv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////4J0ZP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////CvLT/U0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/6idkv///////////3BhTf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZVQf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+OhHP/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwoDf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/2JRPf9FMRj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Dvrb///////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4l+bv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCvp5z/PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OyYL/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/6mglP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AM/Lxf9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VUMs////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/STYe/8/Lxf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFVE - Lf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////Qi4U/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5GGdv///////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////39yYP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9fTzn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6mglP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////OyML/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9zZVL/////////////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBiUT3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////3Bh - Tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP89KQ7/////////////////gXNi/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9jVD//////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ApJqO/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////Qy8X/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/pp2R/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pJuQ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8rEvv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//// - /////////////2ZWQv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AX043/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Sh3j///////// - ////////wry0/6adkf+JfGz/bV1J/1A9Jv85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////z8nC/0k2Hv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/opeM/////////////////5aLfP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/19ON/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJ+WiP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZVVA//////////////////////////////////// - ///////////////////AubH/pJqO/4Z6af9qW0b/TToi/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////hnlo/zsm - C/84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b//////////////////////6ujlv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+PgnT///////////////////////////////////////////////////////// - /////////////////////////////722rf+gl4v/g3hm/2hXQ/9JNh7/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////npSG/2lZRf8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUMs/4+CdP///////////////////////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP8+KQ//W0k0/3doVf+Uinv/sKme/87Iwv////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////AubH/sqqg/6adkf+il4z/opeM/6KX - jP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+imY3/sKme/7+3 - r//OyML//////////////////////////////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Wkgx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0EtE/9eTTb/emxa/5eMfv+0rKL/0czH//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////jIBx/zwoDf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5mQgv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2FPOv98bl3/mZCC/7ewpv////////////////////////////// - //+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/6KZjf9lVUD/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBUQir/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/Y1M+/3VoVP9INRz/OCMI/6ujlv////////////// - /////////////////////////////////////////////////////////////62kmP9xYk//PyoQ/z8q - EP9xYk//raSY//////////////////////////////////////////////////////////////////// - ////////sKme/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/bmBM/2ZV - Qf9OPCT/OyYL/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9UQir/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AlIh5/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////// - //////////////////+il4z/ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/WEcw/5SK - e//Py8X/////////////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/lot8/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA5JAn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+elIb////////////////////////////CvLT/iHtq/006Iv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0c0G/96bFr/tq2j//// - ////////////////////////pJqO/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8ATzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/52R - hP//////r6ec/3NlUv89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/ZlVB/6KXjP//////opmN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/048JP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI+C - dP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+Qg3T/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0MzG//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCKfnD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNR3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2ZVQf+NgXL/tq2j/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9DLxf/ZlVB/42B - cv+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/0MwFv9mVUH/jYFy/7ato/////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDAubH/mI2A/3Bh - Tf9CLhT/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/3BhTf+YjYD/wLmx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/STYe/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8ATToi/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBRPif/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFNA - KP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tzwl//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AVEIq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/UD0m/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBYRzD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VEIq/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AW0k0/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/V0Yu/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBcTDb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/QCsS/3ZnVf+wqZ7/sKme/3ZnVf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEw2//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGFQO/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2Hv+Bc2L/vbat/////wD///8A////AP// - /wD///8A////AMnEvf+NgXL/Tzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEs1/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AYlE9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zkk - Cf9fTzn/m5CD/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Ap56T/21dSf87Jgv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBjVD//OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD/c2VS/6+nnP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AL22 - rf+Bc2L/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/UD4m/4h7 - av/CvLT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDPy8X/lIp7/1dG - Lv84Iwj/OCMI/zgjCP84Iwj/ZlVB/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AbV9K/19POf+bkIP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9tXUn/bFtH//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - //////////5/////////////////////////+B/////////////////////////gB/////////////// - /////////8AD////////////////////////AAD///////////////////////wAAD////////////// - ////////+AAAD//////////////////////gAAAH/////////////////////4AAAAH///////////// - ////wAf+AAAAAH/gA//////////////AAAAAAAAAAAAD/////////////4AAAAAAAAAAAAH///////// - ////AAAAAAAAAAAAAP////////////8AAAAAAAAAAAAA/////////////gAAAAAAAAAAAAB///////// - ///8AAAAAAAAAAAAAD////////////wAAAAAAAAAAAAAP///////////+AAAAAAAAAAAAAAf//////// - ///4AAAAAAAAAAAAAB////////////AAAAAAAAAAAAAAD///////////4AAAAAAAAAAAAAAH//////// - ///gAAAAAAAAAAAAAAf//////////8AAAAAAAAAAAAAAA///////////gAAAAAAAAAAAAAAB//////// - //+AAAAAAAAAAAAAAAH//////////wAAAAAAAAAAAAAAAP/////////+AAAAAAAAAAAAAAAAf/////// - //gAAAAAAAAAAAAAAAAf////////4AAAAAAAAAAAAAAAAAf///////+AAAAAAAAAAAAAAAAAAf////// - /gAAAAAAAAAAAAAAAAAAf//////4AAAAAAAAAAAAAAAAAAAf/////+AAAAAAAAAAAAAAAAAAAAf///// - AAAAAAAAAAAAAAAAAAAAAP////wAAAAAAAAAAAAAAAAAAAAAP///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///4AAAAAAAAAAAAAAAAAAAAAAH///A - AAAAAAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAB//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAD+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAA4AA - AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAYAA - AAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAA+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAD/AA - AAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH//AAAAAAAAAAAAAAAAAAAAAAAA//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/wA - AAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAAAAAAAB///A - AAAAAAAAAAAAAAAAAAAAAAP//+AAAAAAAAAAAAAAAAAAAAAAB///8AAAAAAAAAAAAAAAAAAAAAAP///8 - AAAAAAAAAAAAAAAAAAAAAD////4AAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAAAAAAAAAAAAAD///// - gAAAAAAAAAAAAAAAAAAAAf/////AAAAAAAAAAAAAAAAAAAAD//////AAAAAAAAAAAAAAAAAAAA////// - 8AAAAAAAAAAAAAAAAAAAD//////4AAAAAAAAAAAAAAAAAAAf//////gAAAAAAAAAAAAAAAAAAB////// - +AAAAAAAAAAAAAAAAAAAH//////8AAAAAAAAAAAAAAAAAAA///////wAAAAAAAAAAAAAAAAAAD////// - /AAAAAAAAAAAAAAAAAAAP//////+AAAAAAAAAAAAAAAAAAB///////4AAAAAAAAAAAAAAAAAAH////// - /gAAAAAAAAAAAAAAAAAAf///////AAAAAAAAAAAAAAAAAAD///////8AAAAAAAAAAAAAAAAAAP////// - /wAAAAAAAAAAAAAAAAAA////////gAAAAAAAAAAAAAAAAAH///////+AAAAAAAAAAAAAAAAAAf////// - /4AAAAAAAAAAAAAAAAAB////////gAAAAAAAAAAAAAAAAAH////////AAAAAAAAAAAAAAAAAA/////// - /8AAAAAAAAAAAAAAAAAD////////8AAAAAAAAAAAAAAAAA//////////gAAAAAAAAAAAAAAB//////// - ///8AAAAAAAAAAAAAD/////////////gAAAAAAAAAAAH//////////////4AAAAAAAAAAH////////// - /////wAAAAAAAAAA////////////////gAAAAAAAAAH////////////////AAAAAAAAAA/////////// - /////+AAAAAAAAAH////////////////8AAAAAAAAA/////////////////4AAAAAAAAH/////////// - //////wAAAAAAAA//////////////////gAAAAAAAH//////////////////AAAH4AAA//////////// - //////+AAH/+AAH//////////////////8AD///AA///////////////////4B////gH//////////// - ///////x/////4////////// - - - \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/Login.Designer.cs b/legacy/V3/TGS.ControlPanel/Login.Designer.cs deleted file mode 100644 index e4f9a30693..0000000000 --- a/legacy/V3/TGS.ControlPanel/Login.Designer.cs +++ /dev/null @@ -1,245 +0,0 @@ -namespace TGS.ControlPanel -{ - partial class Login - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Login)); - this.LocalLoginButton = new System.Windows.Forms.Button(); - this.CurrentRevisionTitle = new System.Windows.Forms.Label(); - this.UsernameTextBox = new System.Windows.Forms.TextBox(); - this.PasswordTextBox = new System.Windows.Forms.TextBox(); - this.RemoteLoginButton = new System.Windows.Forms.Button(); - this.AddressLabel = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.label4 = new System.Windows.Forms.Label(); - this.PortSelector = new System.Windows.Forms.NumericUpDown(); - this.SavePasswordCheckBox = new System.Windows.Forms.CheckBox(); - this.IPComboBox = new System.Windows.Forms.ComboBox(); - this.DeleteLoginButton = new System.Windows.Forms.Button(); - ((System.ComponentModel.ISupportInitialize)(this.PortSelector)).BeginInit(); - this.SuspendLayout(); - // - // LocalLoginButton - // - this.LocalLoginButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.LocalLoginButton.Location = new System.Drawing.Point(102, 12); - this.LocalLoginButton.Name = "LocalLoginButton"; - this.LocalLoginButton.Size = new System.Drawing.Size(157, 25); - this.LocalLoginButton.TabIndex = 1; - this.LocalLoginButton.Text = "Connect to Local Service"; - this.LocalLoginButton.UseVisualStyleBackColor = true; - this.LocalLoginButton.Click += new System.EventHandler(this.LocalLoginButton_Click); - // - // CurrentRevisionTitle - // - this.CurrentRevisionTitle.AutoSize = true; - this.CurrentRevisionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.CurrentRevisionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.CurrentRevisionTitle.Location = new System.Drawing.Point(116, 69); - this.CurrentRevisionTitle.Name = "CurrentRevisionTitle"; - this.CurrentRevisionTitle.Size = new System.Drawing.Size(128, 18); - this.CurrentRevisionTitle.TabIndex = 14; - this.CurrentRevisionTitle.Text = "Remote Login:"; - this.CurrentRevisionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // UsernameTextBox - // - this.UsernameTextBox.Location = new System.Drawing.Point(129, 132); - this.UsernameTextBox.Name = "UsernameTextBox"; - this.UsernameTextBox.Size = new System.Drawing.Size(233, 20); - this.UsernameTextBox.TabIndex = 16; - // - // PasswordTextBox - // - this.PasswordTextBox.Location = new System.Drawing.Point(129, 164); - this.PasswordTextBox.Name = "PasswordTextBox"; - this.PasswordTextBox.Size = new System.Drawing.Size(233, 20); - this.PasswordTextBox.TabIndex = 17; - this.PasswordTextBox.UseSystemPasswordChar = true; - // - // RemoteLoginButton - // - this.RemoteLoginButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.RemoteLoginButton.Location = new System.Drawing.Point(102, 200); - this.RemoteLoginButton.Name = "RemoteLoginButton"; - this.RemoteLoginButton.Size = new System.Drawing.Size(157, 25); - this.RemoteLoginButton.TabIndex = 18; - this.RemoteLoginButton.Text = "Connect to Remote Service"; - this.RemoteLoginButton.UseVisualStyleBackColor = true; - this.RemoteLoginButton.Click += new System.EventHandler(this.RemoteLoginButton_Click); - // - // AddressLabel - // - this.AddressLabel.AutoSize = true; - this.AddressLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.AddressLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.AddressLabel.Location = new System.Drawing.Point(9, 99); - this.AddressLabel.Name = "AddressLabel"; - this.AddressLabel.Size = new System.Drawing.Size(80, 18); - this.AddressLabel.TabIndex = 19; - this.AddressLabel.Text = "Address:"; - this.AddressLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label2.Location = new System.Drawing.Point(9, 132); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(97, 18); - this.label2.TabIndex = 20; - this.label2.Text = "Username:"; - this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // label3 - // - this.label3.AutoSize = true; - this.label3.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label3.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label3.Location = new System.Drawing.Point(9, 164); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(92, 18); - this.label3.TabIndex = 21; - this.label3.Text = "Password:"; - this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // label4 - // - this.label4.AutoSize = true; - this.label4.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label4.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label4.Location = new System.Drawing.Point(-19, 40); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(468, 18); - this.label4.TabIndex = 22; - this.label4.Text = "______________________________________________"; - this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // PortSelector - // - this.PortSelector.Location = new System.Drawing.Point(282, 99); - this.PortSelector.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.PortSelector.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.PortSelector.Name = "PortSelector"; - this.PortSelector.Size = new System.Drawing.Size(80, 20); - this.PortSelector.TabIndex = 16; - this.PortSelector.Value = new decimal(new int[] { - 38607, - 0, - 0, - 0}); - // - // SavePasswordCheckBox - // - this.SavePasswordCheckBox.AutoSize = true; - this.SavePasswordCheckBox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.SavePasswordCheckBox.Location = new System.Drawing.Point(265, 205); - this.SavePasswordCheckBox.Name = "SavePasswordCheckBox"; - this.SavePasswordCheckBox.Size = new System.Drawing.Size(100, 17); - this.SavePasswordCheckBox.TabIndex = 23; - this.SavePasswordCheckBox.Text = "Save Password"; - this.SavePasswordCheckBox.UseVisualStyleBackColor = true; - this.SavePasswordCheckBox.CheckedChanged += new System.EventHandler(this.SavePasswordCheckBox_CheckedChanged); - // - // IPComboBox - // - this.IPComboBox.FormattingEnabled = true; - this.IPComboBox.Location = new System.Drawing.Point(130, 98); - this.IPComboBox.Name = "IPComboBox"; - this.IPComboBox.Size = new System.Drawing.Size(146, 21); - this.IPComboBox.TabIndex = 24; - // - // DeleteLoginButton - // - this.DeleteLoginButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.DeleteLoginButton.Location = new System.Drawing.Point(105, 98); - this.DeleteLoginButton.Name = "DeleteLoginButton"; - this.DeleteLoginButton.Size = new System.Drawing.Size(19, 19); - this.DeleteLoginButton.TabIndex = 25; - this.DeleteLoginButton.Text = "x"; - this.DeleteLoginButton.UseVisualStyleBackColor = true; - this.DeleteLoginButton.Click += new System.EventHandler(this.DeleteLoginButton_Click); - // - // Login - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.ClientSize = new System.Drawing.Size(374, 237); - this.Controls.Add(this.DeleteLoginButton); - this.Controls.Add(this.IPComboBox); - this.Controls.Add(this.SavePasswordCheckBox); - this.Controls.Add(this.PortSelector); - this.Controls.Add(this.label4); - this.Controls.Add(this.label3); - this.Controls.Add(this.label2); - this.Controls.Add(this.AddressLabel); - this.Controls.Add(this.RemoteLoginButton); - this.Controls.Add(this.PasswordTextBox); - this.Controls.Add(this.UsernameTextBox); - this.Controls.Add(this.CurrentRevisionTitle); - this.Controls.Add(this.LocalLoginButton); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.Name = "Login"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "Login"; - ((System.ComponentModel.ISupportInitialize)(this.PortSelector)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Button LocalLoginButton; - private System.Windows.Forms.Label CurrentRevisionTitle; - private System.Windows.Forms.TextBox UsernameTextBox; - private System.Windows.Forms.TextBox PasswordTextBox; - private System.Windows.Forms.Button RemoteLoginButton; - private System.Windows.Forms.Label AddressLabel; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.Label label3; - private System.Windows.Forms.Label label4; - private System.Windows.Forms.NumericUpDown PortSelector; - private System.Windows.Forms.CheckBox SavePasswordCheckBox; - private System.Windows.Forms.ComboBox IPComboBox; - private System.Windows.Forms.Button DeleteLoginButton; - } -} \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/Login.cs b/legacy/V3/TGS.ControlPanel/Login.cs deleted file mode 100644 index ff496f167c..0000000000 --- a/legacy/V3/TGS.ControlPanel/Login.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.Windows.Forms; -using TGS.Interface; - -namespace TGS.ControlPanel -{ - sealed partial class Login : Form - { - /// - /// Currently selected saved - /// - RemoteLoginInfo currentLoginInfo; - - bool updatingFields; - - /// - /// Construct a - /// - public Login() - { - InitializeComponent(); - var Config = Properties.Settings.Default; - AcceptButton = RemoteLoginButton; - if(Config.RemoteDefault) - RemoteLoginButton.TabIndex = 0; //make this the first thing selected when loading - - IPComboBox.SelectedIndexChanged += IPComboBox_SelectedIndexChanged; - var loginInfo = Config.RemoteLoginInfo; - if (loginInfo != null) - { - foreach(var I in loginInfo) - IPComboBox.Items.Add(new RemoteLoginInfo(I)); - if (IPComboBox.Items.Count > 0) - IPComboBox.SelectedIndex = 0; - } - - IPComboBox.TextChanged += (a, b) => ClearFields(2); - PortSelector.ValueChanged += (a, b) => ClearFields(3); - UsernameTextBox.TextChanged += (a, b) => ClearFields(4); - - } - - void ClearFields(int start = 1) - { - if (updatingFields || currentLoginInfo == null) - return; - updatingFields = true; - currentLoginInfo = null; - switch (start) { - case 1: - IPComboBox.Text = ""; - goto case 2; - case 2: - PortSelector.Value = 38607; - goto case 3; - case 3: - UsernameTextBox.Text = ""; - goto case 4; - case 4: - if (currentLoginInfo.HasPassword) - PasswordTextBox.Text = ""; - break; - } - updatingFields = false; - } - - void IPComboBox_SelectedIndexChanged(object sender, EventArgs e) - { - RemoteLoginInfo newLoginInfo = IPComboBox.SelectedItem as RemoteLoginInfo; - //new thing - if (newLoginInfo == null) - { - ClearFields(); - currentLoginInfo = newLoginInfo; - return; - } - updatingFields = true; - currentLoginInfo = newLoginInfo; - IPComboBox.Text = currentLoginInfo.IP; - PortSelector.Value = currentLoginInfo.Port; - UsernameTextBox.Text = currentLoginInfo.Username; - SavePasswordCheckBox.Checked = currentLoginInfo.HasPassword; - if (currentLoginInfo.HasPassword) - PasswordTextBox.Text = "************"; - else - PasswordTextBox.Text = ""; - updatingFields = false; - } - - void RemoteLoginButton_Click(object sender, EventArgs e) - { - if (String.IsNullOrWhiteSpace(PasswordTextBox.Text) || String.IsNullOrWhiteSpace(UsernameTextBox.Text) || String.IsNullOrWhiteSpace(IPComboBox.Text) || PortSelector.Value == 0) - return; - - RemoteLoginInfo loginInfo; - if (currentLoginInfo == null) - { - loginInfo = new RemoteLoginInfo(IPComboBox.Text, (ushort)PortSelector.Value, UsernameTextBox.Text.Trim(), PasswordTextBox.Text); - } - else - { - loginInfo = (RemoteLoginInfo)IPComboBox.SelectedItem; - if (!loginInfo.HasPassword) - loginInfo.Password = PasswordTextBox.Text; - } - - var I = new Client(loginInfo); - var Config = Properties.Settings.Default; - //This needs to be read here because V&C Closing us will corrupt the data - var savePassword = SavePasswordCheckBox.Checked; - if (VerifyAndConnect(I)) - { - Config.RemoteDefault = true; - - if (!savePassword) - loginInfo.Password = null; - - Config.RemoteLoginInfo = new StringCollection { loginInfo.ToJSON() }; - - foreach (RemoteLoginInfo info in IPComboBox.Items) - if (!info.Equals(loginInfo)) - Config.RemoteLoginInfo.Add(info.ToJSON()); - } - } - - void LocalLoginButton_Click(object sender, EventArgs e) - { - Properties.Settings.Default.RemoteDefault = false; - VerifyAndConnect(new Client()); - } - - /// - /// Attempts a connection on a given - /// - /// The to attempt a connection on - /// if the connection was made and authenticated, otherwise - bool VerifyAndConnect(IClient I) - { - try - { - var res = I.ConnectionStatus(out string error); - if (!res.HasFlag(ConnectivityLevel.Connected)) - { - MessageBox.Show("Unable to connect to service! Error: " + error); - return false; - } - if (!res.HasFlag(ConnectivityLevel.Authenticated)) - { - MessageBox.Show("Authentication error: Username/password/windows identity is not authorized! Ensure you are a system administrator or in the correct Windows group on the service machine."); - return false; - } - - if (I.VersionMismatch(out error) && MessageBox.Show(error, "Warning", MessageBoxButtons.OKCancel) == DialogResult.Cancel) - return true; - - Close(); - Program.ServerInterface = I; - return true; - } - catch - { - I.Dispose(); - throw; - } - } - - void SavePasswordCheckBox_CheckedChanged(object sender, EventArgs e) - { - if (!updatingFields && !SavePasswordCheckBox.Checked && currentLoginInfo != null) - { - currentLoginInfo = null; - PasswordTextBox.Text = ""; - } - } - - /// - /// Removes the selected item from - /// - /// The sender of the event - /// The - void DeleteLoginButton_Click(object sender, EventArgs e) - { - //make sure we're trying to delete a real item - if (IPComboBox.SelectedItem as RemoteLoginInfo == null) - return; - IPComboBox.Items.RemoveAt(IPComboBox.SelectedIndex); - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/Login.resx b/legacy/V3/TGS.ControlPanel/Login.resx deleted file mode 100644 index a83f34c04b..0000000000 --- a/legacy/V3/TGS.ControlPanel/Login.resx +++ /dev/null @@ -1,2179 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAgIAAAAQAgAKgQAABGDgAAMDAAAAEA - IACoJQAA7h4AAKCgAAABACAAqJwBAJZEAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAA - AAAAAAAAAAAAAAAAAABeTTgAWkgyA1pIMilOOyMoRjMaRz4qEMM+KhDESDUcSU07IyhaSDIpWUgxA15N - NwAAAAAAAAAAAAAAAADSzsgAPyoQAEk2HTk9KQ7ZOSUK5DciB+43Igf/OCQJ/zkkCe46JQrkPSgO2UYz - Gjk8JwwA0s3HAAAAAABVQywAbV1JAk47IytALBK6XEo0/2xcSP9lVUD/mo+C/5CEdf9jUz7/ZFQ//2BP - Ov9LOCC6SzgfK2hYRAJSPygA////AUg1HHA7JgzkOSQJ/29gTP9wYU7/6+nm/+rn5f9rXEj/U0Ap/1NB - Kv9YRzD/Z1ZC/zsmC+RINBtw////AYFzYgU/KhCxOiUK/4V4aP+hl4r/ZFQ///b19P/a1tH/c2RR/87J - w//X083/Y1M+/3BhTv8+Kg//PyoQsYFzYgVYRi8cPSkOz0MvFf/a1tH/vreu/2RTPv/08/H//Pz7//Py - 8P/+/f3/+vn5/3JjUP+Hemr/RjMa/z0pDs9XRS8cQi4VnDciB/xsXEn/9PPx/7y1rP9kUz7/9PLx//// - //////////////f29f9qW0f/oJaJ/3BhTf83Igf8Qi4VnD8qEN45JAn/saie//////+7tKv/ZFM+//Ty - 8f//////7u3q/5SJev95a1n/Y1M9/9rX0v+zq6D/OSQJ/z8qEN5EMBeFNiEF/oV4aP//////vLSr/2RT - Pv/08vH//////+7t6v+UiXr/eWtZ/2NTPv/X0s3/h3pq/zYhBf5EMBeFRzMaZTYhBfuAc2L//////7y0 - q/9kUz7/9PLx//////////////////f29f9qWkX/rqaa/4R3Zv82IQX7RzMaZUUxGIc3Igf/X085/97b - 1/+9tqz/ZFM+//Tz8f/8/Pv/8/Lw//79/f/6+fn/cWJP/5KHeP9kUz7/NyIH/0UxGIdMOSE0PikPvzkk - Cf+Lf2//in5u/2JSPf/29fT/2tbR/3JkUf/PycP/19PN/2NTPv9oWEP/OycM/z0pD79LOCA0RDEYAE89 - JSU/KxHWcGBN/1NAKf9wYU3/9vX0/+rn5P9pWkX/VEIq/1NBKv9YRzD/ZlZB/zwnDdZPPSUlRTEYAGdY - QwAAAAAAQCwSkkw5If9cSjT/bF1J/6ujl/+lm4//j4N0/4R3Z/9lVUD/YE86/0IuFP8/KxGSAAAAAGdY - QwAAAAAAQy8WAFA+JiFDLxZmPCcMnzciBvM5JAn/NyIH/zgjCP87Jgz/OCMI8zwnDZ9DLxVmTzwlIUEs - EwAAAAAAAAAAAAAAAAAAAAAAQi4VAEYyGQc+KQ+GPSgO6EEtE61CLhStPSkP6D0pDoZEMBcHQS0TAAAA - AAAAAAAAAAAAAOAHAADgBwAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB - AADAAwAAwAMAAPAPAAAoAAAAGAAAADAAAAABACAAAAAAAAAJAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wD///8A////AP///wBVQy0AemxaA047I0NCLhTGQS0TxlE+J0aFd2cEYlI9AP// - /wD///8A////AP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF5NNwBhUDsMTToifk47 - I50+KQ+BQy8WmzwnDeg4Iwj/OCMI/z0oDupDLxacPikPgU47I51NOiF+YFA6DF1NNwAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAj4R1AB8HAABLOCBgOiUK+DYhBf82IQX/OCMI/zciB/82IQX/NiEF/zUg - Bf82IQX/NiEF/zYhBf86JQr4SzgfYB8HAACOgnMAAAAAAAAAAAAAAAAAAAAAAAAAAACMgXIACAAAAGBP - OS0/KxHVRzMa/2BPOf9eTTf/QSwT/0UyGf+Lf2//joJy/15NN/9fTjn/X085/11MNv9KNx//PysR1V9O - OC0GAAAAjIFyAAAAAAAAAAAAnpWIANrY0wFaSTIhRTIZdUAsEtc2IQb/d2lW/35xX/+Lf2//vLWs/8/K - xP/7+vr/wLqx/3NkUf9qWkb/alpG/25fS/+GeWj/ZFM+/z8qENdFMRh1WEYvIdTOyQGZjX8AbF1KAG1e - ShxFMhjMOSQJ/jciB/8zHgL/dmhV/1E/KP9oWET/+Pf2///////h3tr/UD4m/zsnDP9EMBb/QzAW/zsm - C/9BLRT/hnlp/0EtFP84Iwj+RTEYzG1dShxsXUkATDkgAE06Iig7JgzoNyIH/z4pD/9wYU3/ubKo/1RB - Kv9qWkb/+Pf3///////Hwbn/OyYL/4p+bv/c2NT/3NjT/6iek/87Jwz/e21b/1E+J/82IQb/OyYM6E06 - IihMOSAAWUcwAF1MNjs9KQ/0NSAE/2pbRv/z8vD/7evo/1JAKf9qWkb/+Pf3///////p5+T/tq6l/9vX - 0v///////////9XRy/9BLRP/in1t/25fS/81HwT/PSkP9F5NNztZRzEAAAAAAEo3H3A6JQr9NB8D/3xu - XP//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XRy/9BLBP/kIR1/35w - Xv80HwP/OiUK/Uo3HnAAAAAAUkApRj4pD+M3Igf/OiUK/6iekv//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/k4d5/6qhlf86JQr/NyIH/z4pD+NSPyhGQy8V1jgj - CP82IQb/emxa//Tz8f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yj - mP83Igf/mI6A//b29P97bVv/NiEG/zgjCP9DLxXWRTEYxDgjCP82IQb/m5CD////////////7Oro/1JA - Kf9qWkb/+Pf3/////////////////6ackP9XRS7/WEYw/0IuFf91Z1T/4d7Z//////+bkIP/NiEG/zgj - CP9FMRjEUT4nXTsmDPU2IAX/XUw2/+3r6f//////7Oro/1JAKf9qWkb/+Pf3/////////////////6ac - kP9XRS7/WEYv/0IuFP92Z1T/4t/b/+7s6v9dTDb/NiAF/zsmDPVRPiddYlE7Gz8qENQ3Igb/SDUc/93a - 1f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yjmP83Igf/mo+B/9/c - 2P9INRz/NyIG/z8qENRhUTwbX044Jj8qEOA2IAX/WUcx/+3r6f//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/kod4/+/t6/9ZRzD/NiAF/z8qEOBfTjgmVEIqRjwn - DfI3IQb/TDkg/8bAuf//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XR - y/9BLBP/kYV2/8nDvP9MOSH/NiEG/zwnDfJUQitGVUMsQj4pD+Q4Iwj/NiEG/047I//PysT/7uzq/1JA - Kf9qWkb/+Pf3///////p5+T/tq6l/9vX0v///////////9XRy/9CLhT/fG5c/1tKNP82IQX/OCMI/z4p - D+RVQyxCwry1Ak07I0Y/KhDUNyIH/0s4IP+akIL/jIBw/0UxGP9rW0f/+Pf3///////Fv7j/OiYL/4p+ - bv/c2NT/3NjT/6iek/87Jwz/eWpY/006Iv83Igf/PioQ1E06Ika8ta0CY1M+AEIvFQBYRjA1PCcN3lA9 - Jf96a1n/NyIH/zQeA/9qW0b/+Pf2///////e29b/TToi/zsmC/9DLxb/QzAW/zsmC/9BLRT/hnlp/0Et - FP89KA7eV0YvNUMwFgBhUDwAAAAAAG5fSwD5+PcBQCwSm0QwF/+FeGj/gnVk/3lrWf+Vinz/9fPy//// - ///39vX/ubGo/4F0Y/9yYk//alpG/25fS/+GeWj/Y1M9/zciB/9BLhSb+fr5AW5gTAAAAAAAAAAAAAAA - AABBLRMASjcfVjsmDPs7Jgv/Qy8W/047I/9aSDL/hHdm/6CWif9xYk7/cWJP/52Thv+DdWX/Xk04/11M - Nv9KNx//OCMH/zsmDPtKNx9WQS0TAAAAAAAAAAAAAAAAAAAAAABcSzUAY1I9E006ImxFMRieQCsSxzgj - CPU2IAX/NiEG/zYhBv81IAT/NSAE/zYhBv82IQb/NSAF/zciB/U+KhDHQy8Wnko3H2xiUTwTXEo0AAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGXiwDJxL0BalpGDT4qEHM5JQrzOCMI/zgjCP86Jgv7OyYL+zgj - CP84Iwj/OSQK8z4pD3NnV0MNt66lAZKGeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzggAFNBKgVBLRSBPysR60QwFrBNOyNpTzwlaUYyGbBALBLrQS0TgVA+JgVJNh0AAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA/4H/APgAHwD4AB8A8AAPAIAAAQCAAAEAgAABAIAAAQCAAAEAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAADAMAAAwDgAAcA4AAHAPgAHwD+AH8AKAAAACAA - AABAAAAAAQAgAAAAAAAAEAAAEwsAABMLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHlrWQCGeWkEVkQtRkUyGclEMBfJU0EpR52UhwZ7blwAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAalpHAAAAAABlVUAmcGBNQG5fTDE8Jw0dXEs1L0g1HIw9KQ7qOCMI/zgjCP8+KQ/sTToikltK - My88Jw0dbl9MMXBgTUBlVUAmAAAAAGdXQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABaSDIAY1I9IEQwF8pBLRP5PysR7TkkCeA+KQ/rOSUK/jgjCP84Iwj/OCMI/zgj - CP85JAr+PSkP6zkkCeA/KxHtQS0T+UQwF8piUTwgWUgxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAeGpXALy2rQJKNx+MOSQK/zchBv83Igb/NyIH/zgiB/84Iwj/NyIH/zUg - Bf81IAX/NyIH/zciB/83Igf/NyIH/zciBv83Igf/OSQK/0o3Hoy6s6oCdmhWAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAANnUzwA0HwMAXk44Rz4pD+05JQr/RTEY/0g1HP9GMhn/OSQJ/zUf - BP9AKxH/e21b/3xuXP9HMxr/RjIZ/0g0HP9INBv/RzQb/0EtFP83Igf/PSgO7V5NN0czHgIA2dTPAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ2ThgAWAAAAYlE8ElRCK2BFMRfVNiEG/1xLNf+pn5T/lIl7/6ee - kv+DdmX/bl9L/763rv/5+Pf/8vHv/66mm/+Wi33/lop8/5aKfP+Wi33/mo+C/4l9bf9HNBv/Qy8W1VRC - K2BhUDsSJxAAAI+CcwAAAAAAAAAAAAAAAAByY1AAg3ZlFVE/J2lALBLFOyYM+jgjCP81IAT/a1tH/4Z5 - af83Igf/e21b//X08//39vX///////v6+f+aj4L/STYd/z4pD/89KA7/PSgO/z0pDv9DLxb/dGZT/5WK - e/8+KQ//OyYL+j8rEcVQPSVpgHJhFW5eSwAAAAAAAAAAAEw6IgBgTzpiPioQ/jgjCP84Iwj/OCMI/zMe - Av9pWUT/g3Zl/zEcAP93aVb//Pv7////////////1tLN/0UyGf83Igf/Qi4U/0MvFv9DLxb/QCwS/zci - B/83IQb/kIR1/15NN/82IAX/OCMI/z4qEP5gTzliTDkhAAAAAAAAAAAAPCgNAEMvFWk5JAn/OCMI/zgj - CP86JQr/V0Uv/66lmv+Ie2v/MRwA/3dpVv/8+/v///////////+5sqn/NSAF/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cWJP/zUgBP84Iwj/OSQJ/0MvFmk8KA0AAAAAAAAAAAAAAAAAU0AphDkk - Cf84Iwj/OCIH/4x/cP/q5+T//////4p+bv8xHAD/d2lW//z7+////////////8fBuv9oWUT/fW9d/+7s - 6v////////////////+Wi3z/Mh0B/3VmU/+YjX//NyIH/zgjCP85JAn/U0EphAAAAAAAAAAAemlYAAAA - AABPPSWYOSQJ/zgjCP87Jgv/vbat////////////in1u/zEcAP93aVb//Pv7/////////////Pz8//j3 - 9v/5+Pj//v7+/////////////////5iMf/8yHQH/d2hW/7ixp/87Jgv/OCMI/zkkCf9PPSWYAAAAAHRm - UgBcSzUAZVQ/J0EtE9M4Iwj/OCMI/z0pDv/JxL3///////////+KfW7/MRwA/3dpVv/8+/v///////// - ////////////////////////////////////////mIx+/zIdAf93aFb/xb+3/z0pD/84Iwj/OCMI/0Et - E9NjUz4nW0kzAHlrWBBJNh2oOSQJ/zgjCP82IQb/U0Ap/+He2v///////////4p9bv8xHAD/d2lW//z7 - +/////////////////////////////////////////////////+YjH7/Mh0B/3VnVP/d2dX/U0Ep/zYh - Bv84Iwj/OSQJ/0g1HKh3aVcQTjsjjDsmC/k4Iwj/NyIH/0AsEv+zq6H/////////////////in1u/zEc - AP93aVb//Pv7/////////////////////////////////////////////////4t/b/8yHAD/c2RR//v7 - +v+0raP/QCwT/zciB/84Iwj/OyYL+U47I4xFMRjoOCMH/zgjCP82IAX/hHdm//r6+f////////////// - //+KfW7/MRwA/3dpVv/8+/v///////////////////////f29f/Szsj/zcjC/87Jw/+wqJ7/Tzwk/zUf - BP+MgHD///7+//v6+v+Ed2f/NiAF/zgjCP84Iwf/RTEY6E48JJs5JAn/OCMI/zUgBf97bVv/+vn5//// - /////////////4p9bv8xHAD/d2lW//z7+///////////////////////4d7Z/1A+Jv89KQ//PioQ/zkk - Cv89KQ//nJGE/+zq5///////+vn5/3ttW/81IAX/OCMI/zkkCf9OPCSbYlI9O0AsEuk4Iwf/NyIH/0Yy - Gf/Tzsj/////////////////in1u/zEcAP93aVb//Pv7///////////////////////h3tn/UD4m/z0p - D/8+KhD/OSQJ/z0pD/+ckYT/7Oro///////Tzsj/RjIZ/zciB/84Iwf/QCwS6WJSPTuooJQFRTEYoDgj - CP84Iwj/NiEG/6KYi/////////////////+KfW7/MRwA/3dpVv/8+/v///////////////////////f2 - 9f/Szsj/zcjC/87Jw/+wqJ3/Tzwk/zUfBP+MgHD//////6KYi/82IQX/OCMI/zgjCP9FMRigqJ+TBQAA - AABOOyOLOSQJ/zgjCP84Iwj/sKid/////////////////4p9bv8xHAD/d2lW//z7+/////////////// - //////////////////////////////////+Lf2//MhwA/3NkUf/+/v7/sKid/zgjCP84Iwj/OSQJ/048 - I4sAAAAAycO7BUk1Ha04Iwj/OCIH/0ArEf/MxsD/////////////////in1u/zEcAP93aVb//Pv7//// - /////////////////////////////////////////////5iMf/8yHQH/dGVS//79/f/Mx8D/QCsR/zgi - B/84Iwj/STYdrcjEvAV8b10UQzAWzDgjCP84Iwf/PikP/62kmf/9/Pz///////////+KfW7/MRwA/3dp - Vv/8+/v/////////////////////////////////////////////////mIx+/zIdAf90ZVL/+fj3/66m - m/8+KQ//OCMH/zgjCP9EMBbMfW9eFGVVQCZBLBPhNyIH/zgjCP83Igf/Qy8V/5yShf/18/L//////4p9 - bv8xHAD/d2lW//z7+/////////////z8/P/49/b/+fj4//7+/v////////////////+YjH//Mh0B/3Rl - Uv+nnZH/Qy8W/zciB/84Iwj/NyIH/0EsE+FlVUAmhHZmDE48JIE8Jw3zOCMI/zgjCP81IAX/VkQt/9bS - zf/+/v7/in1t/zEcAP93aVb//Pv7////////////x8G6/2hZRP99b13/7uzq/////////////////5aL - fP8yHQH/cWJO/3RlUv80HwP/OCMI/zgjCP88Jw3zTTsjgYF0YwxjUj0AfG5dBE88JV4/KxHkOCMI/zUg - BP91Z1T/kIV2/3NlUv9XRS//Mx4C/3dpVv/8+/v///////////+3sKb/NSAE/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cmNP/zUgBP84Iwj/PyoQ5E48JF58blwEY1I9AAAAAAByZVEA////AF5N - OEk+KhDmNR8E/3ZnVP9tXUn/LxkA/zUfBP80HgP/d2hW//z7+////////////9LNx/9CLhX/OCMI/0Iu - FP9DLxb/Qy8W/0AsEv83Igf/NyEG/5CEdf9eTTf/NiAF/z4qEOZdTTdJ////AHBhTgAAAAAAAAAAAAAA - AACBc2MAvreuA0UxGKM1IAX/cWJP/5qQgv9pWUX/Wkgy/0g1HP9/cWD//Pv7////////////+fj4/5SJ - ev9GMhn/OyYM/zwnDf89KA7/PSkO/0MvFv90ZlP/lYp8/z4qD/84Iwj/RTEYo764sAOBdGQAAAAAAAAA - AAAAAAAAAAAAAAAAAAA6JQoASzggXDkkCfxDLxb/bF1J/4ByYf+NgXL/lop8/62kmf/g3dn///////j3 - 9v/f29f/2NTP/8/KxP/CvLT/pZyQ/5WKfP+Wi33/mo+C/4l9bf9HNBv/NyIG/zolCvxLOCBcOiUKAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBdTDYkQCwS4jciB/81HwT/NR8E/zciB/88Jw3/QS0T/1pI - Mv+UiHr/cGBN/0s4H/9KNx//cGBN/5aLff9fTzn/RzMa/0c0G/9BLRT/NyIH/zciBv84Iwj/QCwS4l1M - NiRdTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi39wAI2AcgVWRC1lSDUcqEMvFs0/KxHqOiUK/Dgj - CP84Igf/NyEG/zUgBf81IAT/NyIH/zciB/81IAX/NSAF/zYhBv83Igf/NyIH/zolCvw+KhDqQS0UzUYy - GahUQiplkIR1BYyAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJuRhACzq6IDeGpYEWNT - PipCLhV5OiUK8jgjCP84Iwj/OCMI/zgjCP84Igf/OCIH/zgjCP84Iwj/OCMI/zgjCP85JAryQi4UeWFQ - OypvX0wRpJqOA4x/cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzgfAFVDKwRCLhRtOiYL8jgjCP84Iwj/OiUL+0EsE+dBLRPnOyYL+zgjCP84Iwj/OiUL8kEt - E21RPicESTUdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAU0EqAGBPOgRFMhl9QS0T7Ek2HblNOiJpZFQ/MGhYQzBRPidpSzkguUIu - FOxEMRd9XEw2BFE/KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+B///wAA//4A - AH/8AAA//AAAP/AAAA/AAAADwAAAA8AAAAPAAAADwAAAA4AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACAAAABAAAAAAAAAAAAAAAAAAAAAIAAAAHgAAAH4AAAB/AAAA/wAAAP8AAAD/wAAD//gAH//8AD/ygA - AAAwAAAAYAAAAAEAIAAAAAAAACQAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGxc - SACShXcEaVlFT0o3HstINBzLZFM+T4R3ZwRkUz4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACXjH4AAAAAAGdXQyNRPiaTQCsR6zkkCf85JAn/PyoQ6048JJR+cF8v////AIl8bAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1c/OAGtcSAB0ZVMib2BMU4F0 - Y1OMgHBJSTYdIT4pDx1rW0cmXUw2dEIuFNY6JQr+OCMH/zgjCP84Iwj/OCMI/zolC/9HMxrgW0kzdWtb - RyY+KQ8dSTYdIYyAcEmBdGNTb2BMU3RlUyJrXEgAysjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfW9eAIR3 - Zw1MOSGsQS0T/0UxGP5FMRj4OSQK4zolC+FBLRPmPSkO/DgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP83Igf/PSgO/EEtE+Y6JQvhOSQK40UxGPhFMRj+QS0T/0s5IKyEd2YNfW9dAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACgl4sAAAAAAGFQOmw9KA76NyIH/zciB/83Igf/OCMI/zgjCP84Iwf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjB/84Iwj/OCMI/zciB/83Igf/NyIH/z0oDvpgTzpsAAAAAKKY - jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABXRS4Aa1tHJEQxF9U4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zYhBf82IQX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9EMBfValpGJFZELQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCWiQDEvbUHVEIrmjolCv84Iwj/NiEF/zUgBP81IAT/NSAE/zUg - BP83Igf/OCMI/zgjCP82IQb/OSQJ/2BPOf9gUDr/OSQJ/zUgBP81IAT/NSAE/zUgBP81IAT/NSAE/zUg - BP81IAT/NiEG/zgjCP86JQr/VEEqmsG6sweek4cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl41+AODd2ARtXUleQi4U9DciB/85JAn/YE86/31v - Xf98b13/fG9d/3lrWf9KNx//NB8D/zYgBf9PPSX/n5WI/+zq5//t6+j/oZeK/21dSv94aVf/fG9d/3xv - Xf98b13/fG9d/3ttXP9wYU7/VUMs/zomC/82IQb/Qi4U9GxdSV7h3dkEl41+AAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AGtaRgCNgHAFY1I9OlNBKp9EMBbvOCMI/zgj - CP8+Kg//wbuy/7Wto/+il4v/oZeL/8nEvf+so5j/alpG/4l9bf/b19L//v79////////////7Orn/763 - rv+onpP/opiM/6OYjP+jmIz/opiM/6Sajv+wp53/w721/6ifk/9OOyP/NiEG/0QwFu9TQiqfY1M+Oo2A - cQVrW0cA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wANAAAAqKCUDlxKND9JNh2fQCwS7Tkk - Cf83Igf/OCMI/zgjCP8/KxH/wLqx/11MNv81IAT/NR8E/4+DdP/+/v7/+Pf2//v7+/////////////// - ///Qy8T/Y1I9/z0pD/84Iwj/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TToh/5qPgv+2rqT/RzQb/zYh - Bv85JAn/QCwS7Uc0G59ZRzE/p52RDh4IAAD08e8AAAAAAAAAAAAAAAAAAAAAAJ+WiQCjmo0YX085sj8r - EfM4Iwj/OCMH/zgjCP84Iwj/OCMI/zgjCP8/KxH/wLqx/15NN/82IAX/NSAF/4+DdP////////////// - /////////////+zq5/9jUj3/NB8D/zgjCP84Iwj/OCIH/zgjB/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsm - C/+upZr/in5u/zYgBf84Iwj/OCMH/zgjCP8/KxHzXkw3sqGXihiflIcAAAAAAAAAAAAAAAAAAAAAAIF0 - YwB/cmEtSDUc7TchBv84Iwj/OCMI/zgjCP84Iwj/OCMI/zciB/89KQ7/wLmw/15NN/82IAX/NSAF/4+D - dP///////////////////////////8C6sf88Jw3/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9sXEj/tKyi/zwoDv84Iwj/OCMI/zgjCP83IQb/SDUc7YByYS2CdGQAAAAAAAAA - AAAAAAAAAAAAAFZFLgBWRC0tPysR7TgjCP84Iwj/OCMI/zgjCP83Igf/NSAE/zwoDf9rXEj/1NDK/11M - Nv82IAX/NSAF/4+DdP///////////////////////////6KYi/82IQb/NyIG/0s4IP+6s6n/y8W+/8rE - vP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYhBv9RPyj/vLSr/0g0HP83Igf/OCMI/zgjCP84Iwj/PysR7VZE - LS1XRS4AAAAAAAAAAAAAAAAAAAAAAEEtEwBINBsxOSUK7zgjCP84Iwj/OCMI/zgjB/9ALBL/cGFO/7ix - p//y8O7/8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////5aKfP8yHAH/Mx0C/089 - Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9KNx//vreu/1NBKv83IQb/OCMI/zgj - CP84Iwj/OSUK70c0GzFBLRMAAAAAAAAAAAAAAAAAAAAAAGVUPwB+cF5eQCwS/jgiB/84Iwj/OCMI/zYg - Bf+GeWj/9/b1////////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ix - p/93aFb/d2lX/4p+bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9INBv/1tHM/4p9 - bf81IAX/OCMI/zgjCP84Igf/QCwS/n5wXl5lVD8AAAAAAAAAAAAAAAAAAAAAAFpJMwBsXEhpPysR/zgj - CP84Iwj/OCMI/zYhBv+glYj/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////7+/v/+/f3//v79//7+/v////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/3NjT/6GYi/82IQb/OCMI/zgjCP84Iwf/PysR/2xdSWlbSTMAAAAAAAAAAAAAAAAAn5WIAAAA - AABPPSV9OiYL/zgjCP84Iwj/OCMI/zgjCP+tpZr/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/3NjT/6+nnP84Iwj/OCMI/zgjCP84Iwj/OiYL/089JX0AAAAAn5SIAAAA - AAD///8AY1M+AG5fS0BFMRjnNyIH/zgjCP84Iwj/OCMI/zkkCf+8taz/////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+/uLD/PCgN/zciBv9HNBv/3dnU/764r/85JAn/OCMI/zgjCP84Iwj/NyIH/0Ux - GOdsXUlAYlE8AP///wB7bVsAgHJhH0s4IMY4Iwj/OCMI/zgjCP84Iwj/NyIH/0MvFf/OycP///////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////+/ubD/PCgN/zciBv9HNBv/3NjT/9HMxf9DLxX/NyIH/zgj - CP84Iwj/OCMI/zgjCP9LOCDGf3JgH3ttWwCimYwNUkAomjolC/84Iwj/OCMI/zgjCP84Iwj/OCMI/5CE - df/6+vn/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////++t67/PCcN/zciBv9HNBv/2tbQ//38 - /P+RhXb/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQv/UT8omqCWiQ1ZSDJ6PioQ9jgjB/84Iwj/OCMI/zgj - CP82IAX/bl9L/+3r6f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+qoZX/OCMI/zci - B/9HNBv/2dXQ///////u7Or/cWJO/zYgBf84Iwj/OCMI/zgjCP84Iwf/PioP9llHMXpKNx/tNyIH/zgj - CP84Iwj/OCMI/zYhBv9UQiv/19LN////////////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9PO - yP9fTjj/NiEG/zUgBP9PPCT/4t/c////////////2NTP/1RCK/82IQb/OCMI/zgjCP84Iwj/NyIH/0s3 - H+1XRS+1OiUK/zgjCP84Iwj/OCMI/zUgBP9uX0z/9/b1////////////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP///////////////////////////////////////////+Th3v91Z1T/aFhE/2lZ - Rf9pWUX/ZlVB/0o3Hv83Igf/OyYL/0s4IP+flYj//Pv7////////////9/b1/25fTP81IAT/OCMI/zgj - CP84Iwj/OiUK/1dGL7VrW0hMQCsS7zgjB/84Iwj/OCMI/zciB/9FMRj/zsnC//////////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////////////////////9vX - 0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c1//8+/v/////////////////zsnC/0Ux - GP83Igf/OCMI/zgjCP84Iwf/QCsR72tbR0yglooMUj8ouTgjCP84Iwj/OCMI/zgjCP81IAX/iX1t//7+ - /f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - /////////////9vX0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c2P/8+/v///////// - ///+/f3/iX1t/zUgBf84Iwj/OCMI/zgjCP84Iwj/Uj8ouaGXiwwbAwAAZVVAYD0pD/o4Iwj/OCMI/zgj - CP82IQb/UD4m/+Dd2P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - /////////////////////////////+Th3v91Z1T/aFhE/2lZRf9pWUX/ZlVB/0k2Hv83Igf/OyYL/0s4 - IP+glon//Pv7///////g3Nj/UD4m/zYhBv84Iwj/OCMI/zgjCP89KQ/6ZVVAYBsDAABtXUoAcWNPE0Et - FMY4Iwj/OCMI/zgjCP84Iwf/PSkP/8S+tv//////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9LO - yP9eTjj/NiEG/zUgBP9PPCT/4+Dc///////Evrb/PSkP/zgjCP84Iwj/OCMI/zgjCP9BLRTGcWJQE21d - SgCwp5wAxb63C1I/KL83Igf/OCMI/zgjCP83Igf/RzMa/9nW0P//////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+qoZX/OCMI/zciB/9HNBv/2dXQ///////Z1dD/RjMa/zciB/84Iwj/OCMI/zci - B/9SQCi/xsG4C7CpngByYk8AdmdVFUUxGNU4Iwj/OCMI/zgjCP82IQb/WEYw/+vp5v////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////++t6//PCcN/zciBv9HNBv/2dXQ///////r6eb/WEYv/zYh - Bv84Iwj/OCMI/zgjCP9FMhjVdmhVFXFjUAB+cF4Af3FgNkUxGPA3Igf/OCMI/zgjCP81IAT/b2BN//n5 - +P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////+/ubD/PCgN/zciBv9HNBv/2dXQ//// - ///5+fj/b2BN/zUgBP84Iwj/OCMI/zciB/9FMRjwf3JgNn5wXwBKNh4AXUw2Sj0oDvk4Iwj/OCMI/zgj - CP82IAX/bF1J/+7s6v//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/2dXQ///////v7ev/bV1J/zYgBf84Iwj/OCMI/zgjCP89KA75XUw2Sko3HgA6JgsAY1M+ejwn - Df84Iwj/OCMI/zgjCP84Iwj/OiUL/3lqWP/j4N3/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/2tbR/+bk4P96bFr/OyYL/zgjCP84Iwj/OCMI/zgjCP88Jw3/ZFM+ejsm - CwAAAAAATz0ljDkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zYhBv9hUDr/zsnC//7+/v//////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////7+/v/+/f3//v79//7+/v////////////// - //////////////////+/uLD/PCgN/zciBv9JNR3/x8G5/21dSf82IAX/OCMI/zgjCP84Iwj/OCMI/zgj - CP85JAn/Tz0ljAAAAAAAAAAAcGFOREo2HtQ4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP83Igf/emxa//Dv - 7f//////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ixp/93aFb/d2lX/4p+ - bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9JNh3/urKo/047I/83IQb/OCMI/zgj - CP84Iwj/OCMI/zgjCP9JNh3Ubl9LRAAAAADU0MsAAAAAAGFRPCROOyO3OiYL/TgjB/84Iwj/OCMI/zci - B/9KNh7/x8G5/8zGv//X087/2tbR/1hHMP82IQX/NSAF/4+DdP///////////////////////////5SJ - e/8yHAH/Mx0C/089Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9LNx//u7Oq/088 - JP83Igb/OCMI/zgjCP84Iwf/OiYL/U47I7dhUTwkAAAAANTQywAAAAAAxL61AEUxGQCAcmEUSzggiz8r - Efo4Iwj/OCMI/zciBv9PPSX/u7Oq/048JP9EMRf/UD4m/z4qEP84Iwj/NSAF/4+DdP////////////// - /////////////52Thv82IQb/NyIG/0s4IP+6s6n/y8W+/8rEvP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYh - Bv9RPyj/vLWs/0g1HP83Igf/OCMI/zgjCP8/KxH6Sjcfi31vXRRDLhUAxL61AAAAAAAAAAAAAAAAAAAA - AACFeGgAnJOFCmRUP3w+KhDzOCMI/zciBv9PPCX/urKp/0k2Hf82IQb/NiEG/zgjCP84Iwj/NSAF/4+D - dP///////////////////////////7qyqf86JQr/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9rXEj/tK2i/zwoDv84Iwj/OCMI/z4qEPNjUz58mY+BCoJ2ZQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAjIBwAJyShAhJNh61OCMI/zciBv9PPSX/urKp/0c0G/80HgP/NSAF/zYh - Bv83Igf/NSAE/4+DdP///////////////////////////+Xi3/9ZSDH/NB8E/zgjCP84Iwj/OCIH/zgj - B/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsmC/+tpZr/in5u/zYgBf84Iwj/OCMI/0o3HrWckoUIjIBwAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJGEACgRAABUQituOyYL/zciB/9KNh7/x8G5/6GX - iv+BdGP/bV5K/1hHMP9KNx7/OycM/5CEdf/////////////////////////////////Fv7f/XUw2/zwo - Df83Igf/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TDoh/5qPgf+2rqT/RzQb/zciB/84Iwj/OyYL/1RC - K24oEQAAnJKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBiUjwtQCsR6Dci - B/86JQr/ZVVA/4p+bv+imIz/same/722rf+/uK//uLGn/9XRy//5+Pj///////////////////////z8 - +//9/fz/7evo/8bAuP+so5j/p52S/6Sajv+imIz/opiM/6Sajv+wp5z/w721/6ifk/9OOyP/NiEG/zgj - CP84Iwj/QCsR6GJSPC1dTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHhq - VwCNgnIIRTIYszgjCP84Iwj/NiAF/zUgBP82IQb/OyYM/0MwFv9QPSb/ZFQ//3FiT/9+cF//4d3Z//f2 - 9f/c2NP/saie/31vXf98b13/rqWa/9vY0//5+Pf/4+Hd/4x/cP97blz/fG9d/3ttXP9wYU7/VUMs/zom - C/83Igb/OCMI/zgjCP84Iwj/RTIZs46Dcwh4a1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAMG5sAAvGQAAXEo0ejsmDP83Igf/NyIH/zgjCP84Iwj/OCMI/zciB/83IQb/NSAF/zUg - BP82IQX/YE85/21eSv9JNh3/OSQJ/zUgBP81IAT/OCMI/0g0HP9rW0f/YE86/zYgBf81HwT/NSAE/zUg - BP81IAT/NiEG/zgjCP84Iwj/NyIH/zciB/87Jgz/XEs1ei4YAADEvrQAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYRjAAbl5LLFNBKrZEMRfaQi4U9DwnDfw5JQr/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/NiEF/zUgBf83Igf/OCMI/zgjCP84Iwj/OCMI/zciB/81IAX/NiEF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf87Jgz8QCwS9EIuFdpRPie2bl9MLFhHMQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARTEYALGpngp6bFoaa1xIO2JS - PWRRPyiMTjsjvzolC/A4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQrwTjsjv088JIxdSzVkZFM+O3FiTxqil4sKNCAGAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAPb19AD///8BnpOGCEIuFWY7JgzwOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsmC/BBLRRmnZOGCP///wH29fQAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkMrAG5dSQJGMxlmPCcN7zgjCP84Iwj/OCMI/zgj - CP84Iwf/OCMI/zsmC/87Jgv/OCMI/zciB/84Iwj/OCMI/zgjCP84Iwj/PCcM70QwF2ZmVkMCUT8oAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFlHMABxYU4CSTYdZT0o - Du84Igf/OCMJ/zwnDP1AKxHmSTYdvV1MNoleTTiJTDkhvUEtE+Y8KA39OCQJ/zciB/88KA3vSDUcZWlb - RgJUQywAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AABjUj0Af3FiA007InRFMhjvTjsjw11NN3xlVUAyi39vC////wH///8BlIl7C21dSTJjUz58UT8ow0cz - Gu9MOCB0eGpWA19ONwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///gf//8AAP//8A///wAA//AAAA//AAD/4AAAB/8AAP/gAAAH/wAA/8AAAAP/ - AAD/gAAAAf8AAP8AAAAA/wAA/AAAAAA/AADwAAAAAA8AAOAAAAAABwAA4AAAAAAHAADgAAAAAAcAAOAA - AAAABwAA4AAAAAAHAADgAAAAAAcAAOAAAAAABwAAwAAAAAADAACAAAAAAAEAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA - AAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAMAAAAAAAwAA4AAAAAAH - AADwAAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAA/gAAAAB/AAD+AAAAAH8AAP8A - AAAA/wAA//AAAA//AAD//AAAP/8AAP/+AAB//wAA//8AAP//AAAoAAAAoAAAAEABAAABACAAAAAAAACQ - AQATCwAAEwsAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AHNjUf9zY1H/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Am5CD/z4pD/84Iwj/OCMI/zsnDP+NgXL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/UD0m/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0w4IP+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBvXkv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2dX - RP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI2Bcv87Jgv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/4FzYv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Atq2j/0k1Hf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/6eek/////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AY1Q//zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/X085/9DMxv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCNgXL/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/gXNi/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/9DLxf/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9AKxL/m5CD//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ApJuQ/3ttW/+Ie2r/kIN0/5eMfv+il4z/q6OW/7Ssov+9tq3/yMK6/9DMxv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AysS+/1hHMP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9YRzD/ycS9/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDQzMb/yMK6/722rf+0rKL/q6OW/6KXjP+XjH7/kIN0/4h7 - av97bVv/pJuQ/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9CLhT/Sjcf/1NA - KP9fTzn/Xk02/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/X085/1NAKP9KNx//Qi4U/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zolCv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGJRPf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKmg - lP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBDLxf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wB6alj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP96bFr/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDHwrn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/8O9tf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AU0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5eMfv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKq - oP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/sqqg//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBINRz/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wCDd2X/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OSQJ/1dGLv/Iwrr/yMK6/1dGLv84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/gnRk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS/5uQg/////////////// - ////////p56T/0IuFP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/MxsD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AWEcw/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/emxa////////////////////////////////////////////iHtq/zkk - Cf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8An5aI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZW - Qv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/dGRT/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/0MzG//// - ///////////////////////////////////////////////////QzMb/Xk02/zgjCP84Iwj/Qi4U/1xL - Nf9pWUX/dGRT/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3ps - Wv96bFr/emxa/3psWv9uYEz/ZlVB/048JP86JQr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Av7ev/z8qEP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - ////////////////////////////////////////////////////////RjMZ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf+tpJj///////////////////////////////////////// - ///////////////////////////////////KxL7///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////8/Lxf+hl4n/Y1M+/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PyoQ/7+3r/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wC0rKL/V0Yu/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////////////////////////////////////////////////// - /////////////////////////////3xuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/4l+ - bv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////iX5u/zsnDP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/V0Yu/7Ss - ov////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9OPCT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP/JxL3///////// - //+Ie2r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZlZC//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////8O9 - tf+2raP/qJ2S/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KX - jP+il4z/opeM/6Sajv+yqZ//wLmx/87Iwv//////////////////////////////////////altG/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk/6eek/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Am5CD/0c0G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy///////////////////////Szsj/ycS9/722 - rf+2raP/wryz//////////////////////////////////////////////////////////////////// - /////////////////////////////7Cnnf92Z1X/RjMZ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/1VDLP+Qg3T///////////////////////////+ajoH/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RzQb/52RhP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCQg3T/QS0T/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////pp2R/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r//////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/5CEdf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AI2Bcv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsnDP+kmo7///////// - ////////lIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PCgN/4FzYv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AgXNi/zol - Cv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////VUMs/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////////9mVUH/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/3Nl - Uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMnEvf9sXEj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ZlZC/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/21dSf/KxL7/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Awry0/2NT - Pv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////pp2R/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv////////////////9/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1xLNf+2raP/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AubOq/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////0c0 - G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/dGZU/////////////////zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7mxqP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AKmglP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9eTTb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////9lVUD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/k4d3////////////m5CD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AjIBx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////PCgN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1VELf///////////9HM - x/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AHtuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OSQJ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9/cmD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBvXkv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////6adkf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/PCgN/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/YlI8/0MvF/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8S+t////////////1dGLv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/b15L/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AX085/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+PgnT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - ////////////////////////////////////////////////////////////////////////wbux/1E+ - J/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+onZL///////// - //9wYU3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/19POf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFA+Jv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/q6OW//////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////gnRk/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPib/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBCLhT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1pIMf+2raP///////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////3ZnVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8jCuv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv///////////5CEdf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/7ato////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWkX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////UT4n/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Bc2L///////// - //+YjYD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9wYU3/zsjC//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OiUK/3xuXf////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2hXQ/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////7KqoP86JQr/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDPycL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9oV0P/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/PycL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AwLmx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/1tKNf////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////9bSTT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/wLmx/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKpn/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////aVpF/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7KqoP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCimY3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/empY//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4d7a/////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+GeWj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/lIp7/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIV3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+XjH7///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////lot8/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4Z5aP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB1aFT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pp2R//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////6SbkP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AZ1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Ssov////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+yqqD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEYzGf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/CvLT///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////wry0/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AG9eS/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0czH//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/altG/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJuQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+bkIP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AMzGwP86JQr/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/8jC - uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBMOCD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9JNR3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBsXEj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/RTEY//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////QzAW/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/2lZRf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCXjH7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1RC - Kv////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////1NAKP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/mI2A/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wDGwLj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+elIb///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////+elIb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf/GwLj/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ARzQb/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zkkCf9sXEj///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////25g - TP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZlZC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9KNx////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////TToi/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9iUT3/////AP///wD///8A////AP///wD///8A////AP///wD///8Ak4d3/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jwz/zsjC//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3dl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////9POyP88KA3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5KH - eP////8A////AP///wD///8A////AP///wD///8Aw762/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/opmN//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////opeM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wLmx/////wD///8A////AP// - /wD///8A////AEYzGf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cWJP//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9iUjz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////92Z1X/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0UxGP////8A////AP///wD///8A////AGNUP/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////1E+J/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/YVA7/////wD///8A////AI2Bcv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OycM/9DM - xv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////////////////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+PgnT/////AMK8 - tP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////1pIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3tuXP///////////////////////////////////////////////////////////6ad - kf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7+3r/9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////4l8 - bP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+QhHX///////// - ////////////////////////////////////////////////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9TQCj/lYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1A9Jv////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3tuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wryz//////////////////////////////////// - //////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/l4x+//// - /wA7Jwz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //+5s6r/hnlo/0ArEv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/UT4n//////////////////////////////////////////////////////////////////// - /////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/////wD///8Aemxa/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/w721//////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/8nEvf////////////// - /////////////////////////////////////////////////////////////8K8tP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3ttW/////8A////AM7Iwv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29eS/////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QCsS/7u1rP////////////////////////////////////////////// - //////////////////////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Py8X/////AP// - /wD///8AYU86/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9HNBv/hnlo//// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/////AP///wD///8A////ALSsov84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4p+cP////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/XEs1/722rf////////////////////////////////////////////// - ////////////////////////////////////////////////////////iX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/tKyi/////wD///8A////AP///wD///8ASTYe/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - /////////////////////////////0EtE/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////wD///8A////AP// - /wD///8A////AJiNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3///////////////////////// - /////////////////////////////////////////////////////////////////////////////6Sb - kP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5qOgf////8A////AP///wD///8A////AP///wD///8AOycM/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9QPib///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/fG5d//////////////////////////////////////////////////// - //////////////////////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwo - Df////8A////AP///wD///8A////AP///wD///8A////AHxuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/8G7 - sf////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - ///////////////////AubH/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3/////AP///wD///8A////AP// - /wD///8A////AP///wDSzsj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9sXEj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/vbat//////////////////// - ////////////////////////////////////////////////////////////////////////aVpF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/087I/////wD///8A////AP///wD///8A////AP///wD///8A////AGJS - PP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/4d7a/////////////////////////////// - /////////////////////////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wC2r6T/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+He2v///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/722rf////////////////////////////////////////////// - /////////////4Z6af84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7avpP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEo3H/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP87Jwz/ysS+//////////////////////////////////////////////////////9AKxL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0k2Hv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCbkIP/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/4J0ZP8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1NAKP////////////// - //////////////////////////////////+il4z/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+bkIP/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/bmBM//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3RmVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wry0//////////////////////////////////// - ////////bmBM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP88KA3/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/5GGdv///////////////////////////////////////////4yAcf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/f3Fh/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+vp5z///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP97blz///////// - //////////////////////////////////+tpJj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/0MzG//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubOq/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////zsjC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDMxsD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/zcjB/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Aq6GV/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0w4IP////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////Y1Q//zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////Sjcf/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6ujlv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIl8bP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Jfm7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBoV0P/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/joRz//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////+NgXL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ARzQb/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Cnnf////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////r6ec/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/RzMf///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMrEvv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////OSQJ/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8rGv/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/TToi//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////0w4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+poJT/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiHtq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29e - S/////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - //////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/h3tr/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGZVQf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Qg3T///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////joRz/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBGMxn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////6idkv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////722rf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AycS9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5SKe/////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////////////////////6KXjP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP/KxL7/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cGFN//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////3VoVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qaCU//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCGemn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9UQir///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////1hH - MP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4h7av////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0Iu - FP/CvLP///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////x8C4/0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9mVUH/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEUx - GP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/6KXjP////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////p56T/zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QzAW//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fnFf//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////q6OW/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP// - /wD///8A////AP///wDIwrr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9fTjf///////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/yMK6/////wD///8A////AP///wD///8A////AP///wD///8A////AHxu - Xf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2 - Hv/Py8X//////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fG5d//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Ap56T/zolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////Z1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDEvrf/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9RPif/hnlo/3xuXf////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2VVQP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9DLxf/xL63/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBVRC3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9QPSb///////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9nV0T/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9VRC3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AHNj - Uf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9zY1H/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Alot8/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////8G7sf+7s6v///////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2dXRP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9QPSb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4FzYv///////////5iNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Wi3z/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wC7s6v/PyoQ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/RjMZ/2JSPP98bl3/mI2A/7Ssov/OyML//////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWUX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///JxL3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //+QhHX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/z4pD/+2raP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBPPCX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////dGZU/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/088 - Jf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AGpb - Rv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////4J0ZP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////CvLT/U0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/6idkv///////////3BhTf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZVQf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+OhHP/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwoDf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/2JRPf9FMRj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Dvrb///////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4l+bv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCvp5z/PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OyYL/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/6mglP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AM/Lxf9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VUMs////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/STYe/8/Lxf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFVE - Lf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////Qi4U/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5GGdv///////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////39yYP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9fTzn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6mglP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////OyML/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9zZVL/////////////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBiUT3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////3Bh - Tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP89KQ7/////////////////gXNi/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9jVD//////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ApJqO/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////Qy8X/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/pp2R/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pJuQ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8rEvv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//// - /////////////2ZWQv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AX043/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Sh3j///////// - ////////wry0/6adkf+JfGz/bV1J/1A9Jv85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////z8nC/0k2Hv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/opeM/////////////////5aLfP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/19ON/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJ+WiP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZVVA//////////////////////////////////// - ///////////////////AubH/pJqO/4Z6af9qW0b/TToi/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////hnlo/zsm - C/84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b//////////////////////6ujlv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+PgnT///////////////////////////////////////////////////////// - /////////////////////////////722rf+gl4v/g3hm/2hXQ/9JNh7/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////npSG/2lZRf8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUMs/4+CdP///////////////////////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP8+KQ//W0k0/3doVf+Uinv/sKme/87Iwv////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////AubH/sqqg/6adkf+il4z/opeM/6KX - jP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+imY3/sKme/7+3 - r//OyML//////////////////////////////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Wkgx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0EtE/9eTTb/emxa/5eMfv+0rKL/0czH//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////jIBx/zwoDf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5mQgv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2FPOv98bl3/mZCC/7ewpv////////////////////////////// - //+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/6KZjf9lVUD/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBUQir/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/Y1M+/3VoVP9INRz/OCMI/6ujlv////////////// - /////////////////////////////////////////////////////////////62kmP9xYk//PyoQ/z8q - EP9xYk//raSY//////////////////////////////////////////////////////////////////// - ////////sKme/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/bmBM/2ZV - Qf9OPCT/OyYL/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9UQir/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AlIh5/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////// - //////////////////+il4z/ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/WEcw/5SK - e//Py8X/////////////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/lot8/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA5JAn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+elIb////////////////////////////CvLT/iHtq/006Iv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0c0G/96bFr/tq2j//// - ////////////////////////pJqO/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8ATzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/52R - hP//////r6ec/3NlUv89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/ZlVB/6KXjP//////opmN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/048JP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI+C - dP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+Qg3T/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0MzG//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCKfnD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNR3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2ZVQf+NgXL/tq2j/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9DLxf/ZlVB/42B - cv+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/0MwFv9mVUH/jYFy/7ato/////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDAubH/mI2A/3Bh - Tf9CLhT/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/3BhTf+YjYD/wLmx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/STYe/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8ATToi/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBRPif/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFNA - KP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tzwl//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AVEIq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/UD0m/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBYRzD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VEIq/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AW0k0/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/V0Yu/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBcTDb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/QCsS/3ZnVf+wqZ7/sKme/3ZnVf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEw2//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGFQO/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2Hv+Bc2L/vbat/////wD///8A////AP// - /wD///8A////AMnEvf+NgXL/Tzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEs1/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AYlE9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zkk - Cf9fTzn/m5CD/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Ap56T/21dSf87Jgv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBjVD//OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD/c2VS/6+nnP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AL22 - rf+Bc2L/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/UD4m/4h7 - av/CvLT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDPy8X/lIp7/1dG - Lv84Iwj/OCMI/zgjCP84Iwj/ZlVB/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AbV9K/19POf+bkIP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9tXUn/bFtH//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - //////////5/////////////////////////+B/////////////////////////gB/////////////// - /////////8AD////////////////////////AAD///////////////////////wAAD////////////// - ////////+AAAD//////////////////////gAAAH/////////////////////4AAAAH///////////// - ////wAf+AAAAAH/gA//////////////AAAAAAAAAAAAD/////////////4AAAAAAAAAAAAH///////// - ////AAAAAAAAAAAAAP////////////8AAAAAAAAAAAAA/////////////gAAAAAAAAAAAAB///////// - ///8AAAAAAAAAAAAAD////////////wAAAAAAAAAAAAAP///////////+AAAAAAAAAAAAAAf//////// - ///4AAAAAAAAAAAAAB////////////AAAAAAAAAAAAAAD///////////4AAAAAAAAAAAAAAH//////// - ///gAAAAAAAAAAAAAAf//////////8AAAAAAAAAAAAAAA///////////gAAAAAAAAAAAAAAB//////// - //+AAAAAAAAAAAAAAAH//////////wAAAAAAAAAAAAAAAP/////////+AAAAAAAAAAAAAAAAf/////// - //gAAAAAAAAAAAAAAAAf////////4AAAAAAAAAAAAAAAAAf///////+AAAAAAAAAAAAAAAAAAf////// - /gAAAAAAAAAAAAAAAAAAf//////4AAAAAAAAAAAAAAAAAAAf/////+AAAAAAAAAAAAAAAAAAAAf///// - AAAAAAAAAAAAAAAAAAAAAP////wAAAAAAAAAAAAAAAAAAAAAP///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///4AAAAAAAAAAAAAAAAAAAAAAH///A - AAAAAAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAB//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAD+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAA4AA - AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAYAA - AAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAA+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAD/AA - AAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH//AAAAAAAAAAAAAAAAAAAAAAAA//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/wA - AAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAAAAAAAB///A - AAAAAAAAAAAAAAAAAAAAAAP//+AAAAAAAAAAAAAAAAAAAAAAB///8AAAAAAAAAAAAAAAAAAAAAAP///8 - AAAAAAAAAAAAAAAAAAAAAD////4AAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAAAAAAAAAAAAAD///// - gAAAAAAAAAAAAAAAAAAAAf/////AAAAAAAAAAAAAAAAAAAAD//////AAAAAAAAAAAAAAAAAAAA////// - 8AAAAAAAAAAAAAAAAAAAD//////4AAAAAAAAAAAAAAAAAAAf//////gAAAAAAAAAAAAAAAAAAB////// - +AAAAAAAAAAAAAAAAAAAH//////8AAAAAAAAAAAAAAAAAAA///////wAAAAAAAAAAAAAAAAAAD////// - /AAAAAAAAAAAAAAAAAAAP//////+AAAAAAAAAAAAAAAAAAB///////4AAAAAAAAAAAAAAAAAAH////// - /gAAAAAAAAAAAAAAAAAAf///////AAAAAAAAAAAAAAAAAAD///////8AAAAAAAAAAAAAAAAAAP////// - /wAAAAAAAAAAAAAAAAAA////////gAAAAAAAAAAAAAAAAAH///////+AAAAAAAAAAAAAAAAAAf////// - /4AAAAAAAAAAAAAAAAAB////////gAAAAAAAAAAAAAAAAAH////////AAAAAAAAAAAAAAAAAA/////// - /8AAAAAAAAAAAAAAAAAD////////8AAAAAAAAAAAAAAAAA//////////gAAAAAAAAAAAAAAB//////// - ///8AAAAAAAAAAAAAD/////////////gAAAAAAAAAAAH//////////////4AAAAAAAAAAH////////// - /////wAAAAAAAAAA////////////////gAAAAAAAAAH////////////////AAAAAAAAAA/////////// - /////+AAAAAAAAAH////////////////8AAAAAAAAA/////////////////4AAAAAAAAH/////////// - //////wAAAAAAAA//////////////////gAAAAAAAH//////////////////AAAH4AAA//////////// - //////+AAH/+AAH//////////////////8AD///AA///////////////////4B////gH//////////// - ///////x/////4////////// - - - \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/Program.cs b/legacy/V3/TGS.ControlPanel/Program.cs deleted file mode 100644 index 50768d847a..0000000000 --- a/legacy/V3/TGS.ControlPanel/Program.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Windows.Forms; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.ControlPanel -{ - static class Program - { - public static IClient ServerInterface; - [STAThread] - static void Main(string[] args) - { - try - { - if (Properties.Settings.Default.UpgradeRequired) - { - Properties.Settings.Default.Upgrade(); - Properties.Settings.Default.UpgradeRequired = false; - Properties.Settings.Default.Save(); - } - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Interface.Client.SetBadCertificateHandler(BadCertificateHandler); - Application.Run(new Login()); - if(ServerInterface != null) - { - new InstanceSelector(ServerInterface.Server).Show(); - Application.Run(); - } - } - catch (Exception e) - { - if (ServerInterface != null) - ServerInterface.Dispose(); - ServiceDisconnectException(e); - } - finally - { - Properties.Settings.Default.Save(); - } - } - static bool SSLErrorPromptResult = false; - static bool BadCertificateHandler(string message) - { - if (!SSLErrorPromptResult) - { - var result = MessageBox.Show(message + " IT IS HIGHLY RECCOMENDED YOU DO NOT PROCEED! Continue?", "SSL Error", MessageBoxButtons.YesNo) == DialogResult.Yes; - SSLErrorPromptResult = result; - return result; - } - return true; - } - - public static void ServiceDisconnectException(Exception e) - { - MessageBox.Show("An unhandled exception occurred. This usually means we lost connection to the service. Error" + e.ToString()); - } - - public static string TextPrompt(string caption, string text) - { - Form prompt = new Form() - { - Width = 500, - Height = 150, - FormBorderStyle = FormBorderStyle.FixedDialog, - Text = caption, - StartPosition = FormStartPosition.CenterScreen, - MaximizeBox = false, - MinimizeBox = false, - }; - Label textLabel = new Label() { Left = 50, Top = 20, Text = text, AutoSize = true }; - TextBox textBox = new TextBox() { Left = 50, Top = 50, Width = 400 }; - Button confirmation = new Button() { Text = "Ok", Left = 350, Width = 100, Top = 70, DialogResult = DialogResult.OK }; - confirmation.Click += (sender, e) => { prompt.Close(); }; - prompt.Controls.Add(textBox); - prompt.Controls.Add(confirmation); - prompt.Controls.Add(textLabel); - prompt.AcceptButton = confirmation; - - return prompt.ShowDialog() == DialogResult.OK ? textBox.Text : null; - } - - /// - /// Prompts the user to open the explaining the API rate limit - /// - /// The to use - /// if the ran and returned a , otherwise - public static bool RateLimitPrompt(Octokit.GitHubClient client) - { - if (MessageBox.Show("You seem to have hit the rate limit of 60 requests per hour of the GitHub API for anonymous requests. Would you like to enter credentials to bypass this?", "Rate limited", MessageBoxButtons.YesNo) != DialogResult.Yes) - return false; - using (var D = new GitHubLoginPrompt(client)) - return D.ShowDialog() == DialogResult.OK; - } - - /// - /// Gets the and of a given 's remote. Shows an error if the target remote isn't GitHub - /// - /// The to get the remote of - /// The owner of the remote - /// The remote - /// if 's remote was a valid GitHub , otherwise and the user was prompted - public static bool GetRepositoryRemote(ITGRepository repo, out string owner, out string name) - { - string remote = null; - remote = repo.GetRemote(out string error); - if (remote == null) - { - MessageBox.Show(String.Format("Error retrieving remote repository: {0}", error)); - owner = null; - name = null; - return false; - } - if (!remote.Contains("github.com")) - { - MessageBox.Show("Pull request support is only available for GitHub based repositories!", "Error"); - owner = null; - name = null; - return false; - } - - //Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1] - //Yes use .git twice in case it was weird - var toRemove = new string[] { ".git", "/", ".git" }; - foreach (string item in toRemove) - if (remote.EndsWith(item)) - remote = remote.Substring(0, remote.LastIndexOf(item)); - var splits = remote.Split('/'); - name = splits[splits.Length - 1]; - owner = splits[splits.Length - 2].Split('.')[0]; - return true; - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/Properties/AssemblyInfo.cs b/legacy/V3/TGS.ControlPanel/Properties/AssemblyInfo.cs deleted file mode 100644 index 1f05bee96e..0000000000 --- a/legacy/V3/TGS.ControlPanel/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TGStation Server Control Panel")] -[assembly: AssemblyDescription("Control panel for the TG Station Server Service")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("394e7643-6b8c-416f-ab18-95ac12648cdc")] diff --git a/legacy/V3/TGS.ControlPanel/Properties/Settings.Designer.cs b/legacy/V3/TGS.ControlPanel/Properties/Settings.Designer.cs deleted file mode 100644 index 391f1eca5b..0000000000 --- a/legacy/V3/TGS.ControlPanel/Properties/Settings.Designer.cs +++ /dev/null @@ -1,121 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace TGS.ControlPanel.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("0")] - public int LastPageIndex { - get { - return ((int)(this["LastPageIndex"])); - } - set { - this["LastPageIndex"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("0")] - public int LastConfigPageIndex { - get { - return ((int)(this["LastConfigPageIndex"])); - } - set { - this["LastConfigPageIndex"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("True")] - public bool UpgradeRequired { - get { - return ((bool)(this["UpgradeRequired"])); - } - set { - this["UpgradeRequired"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("0")] - public int LastChatProvider { - get { - return ((int)(this["LastChatProvider"])); - } - set { - this["LastChatProvider"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - public global::System.Collections.Specialized.StringCollection RemoteLoginInfo { - get { - return ((global::System.Collections.Specialized.StringCollection)(this["RemoteLoginInfo"])); - } - set { - this["RemoteLoginInfo"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] - public bool RemoteDefault { - get { - return ((bool)(this["RemoteDefault"])); - } - set { - this["RemoteDefault"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string GitHubAPIKey { - get { - return ((string)(this["GitHubAPIKey"])); - } - set { - this["GitHubAPIKey"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string GitHubAPIKeyEntropy { - get { - return ((string)(this["GitHubAPIKeyEntropy"])); - } - set { - this["GitHubAPIKeyEntropy"] = value; - } - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/Properties/Settings.settings b/legacy/V3/TGS.ControlPanel/Properties/Settings.settings deleted file mode 100644 index f2be0bac77..0000000000 --- a/legacy/V3/TGS.ControlPanel/Properties/Settings.settings +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - 0 - - - 0 - - - True - - - 0 - - - - - - False - - - - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/ServerOpForm.cs b/legacy/V3/TGS.ControlPanel/ServerOpForm.cs deleted file mode 100644 index 5188cd2b31..0000000000 --- a/legacy/V3/TGS.ControlPanel/ServerOpForm.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace TGS.ControlPanel -{ - /// - /// Used to provide an ATP function for calls into an - /// -#if !DEBUG - abstract -#endif - class ServerOpForm : Form - { - /// - /// Used to wrap calls in a non-blocking fashion while disabling the and enabling the wait cursor - /// - /// The operation to wrap - /// A wrapping - protected Task WrapServerOp(Action action) - { - Enabled = false; - UseWaitCursor = true; - try - { - return Task.Run(action); - } - finally - { - Enabled = true; - UseWaitCursor = false; - } - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/TGS.ControlPanel.csproj b/legacy/V3/TGS.ControlPanel/TGS.ControlPanel.csproj deleted file mode 100644 index 3f5b25b9e5..0000000000 --- a/legacy/V3/TGS.ControlPanel/TGS.ControlPanel.csproj +++ /dev/null @@ -1,151 +0,0 @@ - - - - - Debug - AnyCPU - {394E7643-6B8C-416F-AB18-95AC12648CDC} - WinExe - TGS.ControlPanel - TGControlPanel - v4.5.2 - 512 - true - - - - - tgs.ico - - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\Release\ - TRACE - bin\x86\Release\TGS.ControlPanel.xml - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - false - - - - ..\packages\Octokit.0.29.0\lib\net45\Octokit.dll - - - - - - C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Windows.Forms.dll - - - - - - Form - - - Form - - - Form - - - Form - - - GitHubLoginPrompt.cs - - - Form - - - InstanceSelector.cs - - - Form - - - Login.cs - - - Form - - - ControlPanel.cs - - - - - True - True - Settings.settings - - - Form - - - Form - - - - Form - - - Form - - - TestMergeManager.cs - - - Form - - - GitHubLoginPrompt.cs - - - InstanceSelector.cs - - - Login.cs - - - ControlPanel.cs - - - TestMergeManager.cs - - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} - TGS.Interface - - - - - - - diff --git a/legacy/V3/TGS.ControlPanel/TestMergeManager.Designer.cs b/legacy/V3/TGS.ControlPanel/TestMergeManager.Designer.cs deleted file mode 100644 index e73ba3944e..0000000000 --- a/legacy/V3/TGS.ControlPanel/TestMergeManager.Designer.cs +++ /dev/null @@ -1,222 +0,0 @@ -namespace TGS.ControlPanel -{ - partial class TestMergeManager - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TestMergeManager)); - this.PullRequestListBox = new System.Windows.Forms.CheckedListBox(); - this.ApplyButton = new System.Windows.Forms.Button(); - this.UpdateToRemoteRadioButton = new System.Windows.Forms.RadioButton(); - this.UpdateToOriginRadioButton = new System.Windows.Forms.RadioButton(); - this.NoUpdateRadioButton = new System.Windows.Forms.RadioButton(); - this.RefreshButton = new System.Windows.Forms.Button(); - this.ApplyingPullRequestsLabel = new System.Windows.Forms.Label(); - this.ApplyingPullRequestsProgressBar = new System.Windows.Forms.ProgressBar(); - this.AddPRButton = new System.Windows.Forms.Button(); - this.AddPRNumericUpDown = new System.Windows.Forms.NumericUpDown(); - ((System.ComponentModel.ISupportInitialize)(this.AddPRNumericUpDown)).BeginInit(); - this.SuspendLayout(); - // - // PullRequestListBox - // - this.PullRequestListBox.CheckOnClick = true; - this.PullRequestListBox.FormattingEnabled = true; - this.PullRequestListBox.Location = new System.Drawing.Point(13, 13); - this.PullRequestListBox.Name = "PullRequestListBox"; - this.PullRequestListBox.Size = new System.Drawing.Size(588, 199); - this.PullRequestListBox.TabIndex = 0; - this.PullRequestListBox.ThreeDCheckBoxes = true; - this.PullRequestListBox.UseWaitCursor = true; - // - // ApplyButton - // - this.ApplyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.ApplyButton.Location = new System.Drawing.Point(544, 226); - this.ApplyButton.Name = "ApplyButton"; - this.ApplyButton.Size = new System.Drawing.Size(57, 23); - this.ApplyButton.TabIndex = 2; - this.ApplyButton.Text = "Apply"; - this.ApplyButton.UseVisualStyleBackColor = true; - this.ApplyButton.UseWaitCursor = true; - this.ApplyButton.Click += new System.EventHandler(this.ApplyButton_Click); - // - // UpdateToRemoteRadioButton - // - this.UpdateToRemoteRadioButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.UpdateToRemoteRadioButton.AutoSize = true; - this.UpdateToRemoteRadioButton.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.UpdateToRemoteRadioButton.Location = new System.Drawing.Point(13, 229); - this.UpdateToRemoteRadioButton.Name = "UpdateToRemoteRadioButton"; - this.UpdateToRemoteRadioButton.Size = new System.Drawing.Size(116, 17); - this.UpdateToRemoteRadioButton.TabIndex = 3; - this.UpdateToRemoteRadioButton.TabStop = true; - this.UpdateToRemoteRadioButton.Text = "Update To Remote"; - this.UpdateToRemoteRadioButton.UseVisualStyleBackColor = true; - this.UpdateToRemoteRadioButton.UseWaitCursor = true; - // - // UpdateToOriginRadioButton - // - this.UpdateToOriginRadioButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.UpdateToOriginRadioButton.AutoSize = true; - this.UpdateToOriginRadioButton.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.UpdateToOriginRadioButton.Location = new System.Drawing.Point(135, 229); - this.UpdateToOriginRadioButton.Name = "UpdateToOriginRadioButton"; - this.UpdateToOriginRadioButton.Size = new System.Drawing.Size(106, 17); - this.UpdateToOriginRadioButton.TabIndex = 4; - this.UpdateToOriginRadioButton.TabStop = true; - this.UpdateToOriginRadioButton.Text = "Update To Origin"; - this.UpdateToOriginRadioButton.UseVisualStyleBackColor = true; - this.UpdateToOriginRadioButton.UseWaitCursor = true; - // - // NoUpdateRadioButton - // - this.NoUpdateRadioButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.NoUpdateRadioButton.AutoSize = true; - this.NoUpdateRadioButton.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.NoUpdateRadioButton.Location = new System.Drawing.Point(247, 229); - this.NoUpdateRadioButton.Name = "NoUpdateRadioButton"; - this.NoUpdateRadioButton.Size = new System.Drawing.Size(77, 17); - this.NoUpdateRadioButton.TabIndex = 5; - this.NoUpdateRadioButton.TabStop = true; - this.NoUpdateRadioButton.Text = "No Update"; - this.NoUpdateRadioButton.UseVisualStyleBackColor = true; - this.NoUpdateRadioButton.UseWaitCursor = true; - // - // RefreshButton - // - this.RefreshButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.RefreshButton.Location = new System.Drawing.Point(481, 226); - this.RefreshButton.Name = "RefreshButton"; - this.RefreshButton.Size = new System.Drawing.Size(57, 23); - this.RefreshButton.TabIndex = 6; - this.RefreshButton.Text = "Refresh"; - this.RefreshButton.UseVisualStyleBackColor = true; - this.RefreshButton.UseWaitCursor = true; - this.RefreshButton.Click += new System.EventHandler(this.RefreshButton_Click); - // - // ApplyingPullRequestsLabel - // - this.ApplyingPullRequestsLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.ApplyingPullRequestsLabel.AutoSize = true; - this.ApplyingPullRequestsLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ApplyingPullRequestsLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ApplyingPullRequestsLabel.Location = new System.Drawing.Point(224, 92); - this.ApplyingPullRequestsLabel.Name = "ApplyingPullRequestsLabel"; - this.ApplyingPullRequestsLabel.Size = new System.Drawing.Size(156, 16); - this.ApplyingPullRequestsLabel.TabIndex = 8; - this.ApplyingPullRequestsLabel.Text = "Applying Pull Requests..."; - this.ApplyingPullRequestsLabel.UseWaitCursor = true; - this.ApplyingPullRequestsLabel.Visible = false; - // - // ApplyingPullRequestsProgressBar - // - this.ApplyingPullRequestsProgressBar.Location = new System.Drawing.Point(48, 112); - this.ApplyingPullRequestsProgressBar.Name = "ApplyingPullRequestsProgressBar"; - this.ApplyingPullRequestsProgressBar.Size = new System.Drawing.Size(513, 23); - this.ApplyingPullRequestsProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee; - this.ApplyingPullRequestsProgressBar.TabIndex = 9; - this.ApplyingPullRequestsProgressBar.UseWaitCursor = true; - this.ApplyingPullRequestsProgressBar.Visible = false; - // - // AddPRButton - // - this.AddPRButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.AddPRButton.Location = new System.Drawing.Point(418, 226); - this.AddPRButton.Name = "AddPRButton"; - this.AddPRButton.Size = new System.Drawing.Size(57, 23); - this.AddPRButton.TabIndex = 10; - this.AddPRButton.Text = "Add PR"; - this.AddPRButton.UseVisualStyleBackColor = true; - this.AddPRButton.UseWaitCursor = true; - this.AddPRButton.Click += new System.EventHandler(this.AddPRButton_Click); - // - // AddPRNumericUpDown - // - this.AddPRNumericUpDown.Location = new System.Drawing.Point(331, 229); - this.AddPRNumericUpDown.Maximum = new decimal(new int[] { - 10000000, - 0, - 0, - 0}); - this.AddPRNumericUpDown.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.AddPRNumericUpDown.Name = "AddPRNumericUpDown"; - this.AddPRNumericUpDown.Size = new System.Drawing.Size(81, 20); - this.AddPRNumericUpDown.TabIndex = 11; - this.AddPRNumericUpDown.UseWaitCursor = true; - this.AddPRNumericUpDown.Value = new decimal(new int[] { - 1, - 0, - 0, - 0}); - // - // TestMergeManager - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.ClientSize = new System.Drawing.Size(613, 261); - this.Controls.Add(this.AddPRNumericUpDown); - this.Controls.Add(this.AddPRButton); - this.Controls.Add(this.ApplyingPullRequestsProgressBar); - this.Controls.Add(this.ApplyingPullRequestsLabel); - this.Controls.Add(this.RefreshButton); - this.Controls.Add(this.NoUpdateRadioButton); - this.Controls.Add(this.UpdateToOriginRadioButton); - this.Controls.Add(this.UpdateToRemoteRadioButton); - this.Controls.Add(this.ApplyButton); - this.Controls.Add(this.PullRequestListBox); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.Name = "TestMergeManager"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "Test Merge Manager"; - this.UseWaitCursor = true; - ((System.ComponentModel.ISupportInitialize)(this.AddPRNumericUpDown)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.CheckedListBox PullRequestListBox; - private System.Windows.Forms.Button ApplyButton; - private System.Windows.Forms.RadioButton UpdateToRemoteRadioButton; - private System.Windows.Forms.RadioButton UpdateToOriginRadioButton; - private System.Windows.Forms.RadioButton NoUpdateRadioButton; - private System.Windows.Forms.Button RefreshButton; - private System.Windows.Forms.Label ApplyingPullRequestsLabel; - private System.Windows.Forms.ProgressBar ApplyingPullRequestsProgressBar; - private System.Windows.Forms.Button AddPRButton; - private System.Windows.Forms.NumericUpDown AddPRNumericUpDown; - } -} \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/TestMergeManager.cs b/legacy/V3/TGS.ControlPanel/TestMergeManager.cs deleted file mode 100644 index b463c444dc..0000000000 --- a/legacy/V3/TGS.ControlPanel/TestMergeManager.cs +++ /dev/null @@ -1,384 +0,0 @@ -using Octokit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.ControlPanel -{ - sealed partial class TestMergeManager : ServerOpForm - { - /// - /// Error message format used when fails - /// - const string MergedPullsError = "Error retrieving currently merged pull requests: {0}"; - - /// - /// The to handle pull requests for - /// - readonly IInstance currentInterface; - - /// - /// The to use to read PR lists - /// - readonly GitHubClient client; - - /// - /// The owner of the target - /// - string repoOwner; - /// - /// The name of the target - /// - string repoName; - - /// - /// Construct a - /// - /// The to manage pull requests for - /// The to use for getting pull request information - public TestMergeManager(IInstance interfaceToUse, GitHubClient clientToUse) - { - InitializeComponent(); - DialogResult = DialogResult.Cancel; - UpdateToRemoteRadioButton.Checked = true; - currentInterface = interfaceToUse; - client = clientToUse; - Load += PullRequestManager_Load; - PullRequestListBox.ItemCheck += PullRequestListBox_ItemCheck; - } - - /// - /// Called when an item in is checked or unchecked. Unchecks opposing PRs - /// - /// The sender of the event - /// The - void PullRequestListBox_ItemCheck(object sender, ItemCheckEventArgs e) - { - if (e.NewValue != CheckState.Checked) - return; - //let's uncheck the opposing PR# if this is one of the outdated/updated testmerge - var item = (string)PullRequestListBox.Items[e.Index]; - var prefix = item.Split(' ')[0]; - - for (var I = 0; I < PullRequestListBox.Items.Count; ++I) - { - var S = (string)PullRequestListBox.Items[I]; - if (S.Split(' ')[0] == prefix && S != item) - { - PullRequestListBox.SetItemChecked(I, false); - break; - } - } - } - - /// - /// Populate with from github and check off ones that are currently merged. Prompts the user to login to GitHub if they hit the rate limit - /// - async void LoadPullRequests() - { - try - { - Enabled = false; - UseWaitCursor = true; - try - { - PullRequestListBox.Items.Clear(); - - var repo = currentInterface.Repository; - string error = null; - List pulls = null; - - //get started on this while we're processing here - var pullsRequest = Task.Run(() => pulls = repo.MergedPullRequests(out error)); - - //Search for open PRs - Enabled = false; - UseWaitCursor = true; - SearchIssuesResult result; - try - { - result = await client.Search.SearchIssues(new SearchIssuesRequest - { - Repos = new RepositoryCollection { { repoOwner, repoName } }, - State = ItemState.Open, - Type = IssueTypeQualifier.PullRequest - }); - } - finally - { - Enabled = true; - UseWaitCursor = false; - } - - //now we need to know what's merged - await pullsRequest; - if (pulls == null) - MessageBox.Show(String.Format(MergedPullsError, error)); - - //insert the open pull requests, checking already merged once - foreach (var I in result.Items) - if (!pulls.Any(x => x.Number == I.Number)) - InsertPullRequest(I, false, CheckState.Unchecked); - - //insert remaining merged pulls - foreach (var I in pulls) - { - var pr = await client.PullRequest.Get(repoOwner, repoName, I.Number); - var outdated = pr.Head.Sha != I.Sha; - var mergedOrOutdated = pr.Merged || outdated; - InsertPullRequest(await client.Issue.Get(repoOwner, repoName, I.Number), true, mergedOrOutdated ? CheckState.Indeterminate : CheckState.Checked, String.Format("{1}{0}", mergedOrOutdated ? String.Format(" - {0}", I.Sha) : String.Empty, pr.Merged ? " - MERGED ON REMOTE: " : (outdated ? " - OUTDATED: " : String.Empty))); - } - } - finally - { - Enabled = true; - UseWaitCursor = false; - } - } - catch (ForbiddenException) - { - if (client.Credentials.AuthenticationType == AuthenticationType.Anonymous) //assume request limit hit - { - if(Program.RateLimitPrompt(client)) - LoadPullRequests(); - } - else - throw; - } - } - - /// - /// Format an entry for and insert it into - /// - /// The to format, must contain a - /// If this or is mpt , will be inserted at the top of as opposed to the bottom - /// The of the item - /// An optional to append to the entry before it's inserted - void InsertPullRequest(Issue issue, bool prioritize, CheckState checkState, string append = null) - { - bool needsTesting = issue.Labels.Any(x => x.Name.ToLower().Contains("test")); - prioritize |= needsTesting; - var itemString = String.Format("#{0} - {1}{2}{3}", issue.Number, issue.Title, needsTesting ? " - TESTING REQUESTED" : String.Empty, append ?? String.Empty); - InsertItem(itemString, prioritize, checkState); - } - - /// - /// Insert an into - /// - /// The to insert - /// If this or is not , will be inserted at the top of as opposed to the bottom - /// The of the item - void InsertItem(string itemString, bool prioritize, CheckState checkState) - { - prioritize = prioritize || checkState != CheckState.Unchecked; - if (prioritize) - { - PullRequestListBox.Items.Insert(0, itemString); - PullRequestListBox.SetItemCheckState(0, checkState); - } - else - PullRequestListBox.Items.Add(itemString, checkState); - } - - /// - /// Called when the is loaded. Sets and - /// - /// The sender of the event - /// The - void PullRequestManager_Load(object sender, EventArgs e) - { - Enabled = false; - var repo = currentInterface.Repository; - if(!Program.GetRepositoryRemote(repo, out repoOwner, out repoName)) - { - Close(); - return; - } - LoadPullRequests(); - } - - /// - /// Calls and shows the user an error prompt if it fails - /// - /// The to call on - async void GenerateChangelog(ITGRepository repo) - { - string error = null; - await WrapServerOp(() => repo.GenerateChangelog(out error)); - if (error != null) - MessageBox.Show(String.Format("Error generating changelog: {0}", error)); - } - - /// - /// Called when the is clicked. Calls if necessary, merge pull requests, and call . Closes the if appropriate - /// - /// The sender of the event - /// The - async void ApplyButton_Click(object sender, EventArgs e) - { - Enabled = false; - UseWaitCursor = true; - ApplyingPullRequestsLabel.Visible = true; - ApplyingPullRequestsProgressBar.Visible = true; - PullRequestListBox.Visible = false; - try - { - //so first collect a list of pulls that are checked - var pulls = new List(); - foreach (int I in PullRequestListBox.CheckedIndices) - { - var S = (string)PullRequestListBox.Items[I]; - string mergedSha = null; - var splits = S.Split(' '); - var mergedOnRemote = S.Contains(" - MERGED ON REMOTE: "); - if (mergedOnRemote && UpdateToRemoteRadioButton.Checked) - continue; - if ((S.Contains(" - OUTDATED: " ) || mergedOnRemote) && PullRequestListBox.GetItemCheckState(I) == CheckState.Indeterminate) - mergedSha = splits[splits.Length - 1]; - var key = Convert.ToInt32((splits[0].Substring(1))); - try - { - pulls.Add(new PullRequestInfo(key, mergedSha)); - } - catch - { - MessageBox.Show(String.Format("Checked both keep and update option for #{0}", key), "Error"); - return; - } - } - - var repo = currentInterface.Repository; - - string error = null; - //Do standard repo updates - if (UpdateToRemoteRadioButton.Checked) - await WrapServerOp(() => error = repo.Update(true)); - else if (UpdateToOriginRadioButton.Checked) - await WrapServerOp(() => error = repo.Reset(true)); - - if (error != null) - { - MessageBox.Show(String.Format("Error updating repository: {0}", error)); - return; - } - - if (UpdateToRemoteRadioButton.Checked) - { - GenerateChangelog(repo); - error = await Task.Run(() => repo.SynchronizePush()); - } - - //Merge the PRs, collect errors - var errors = await Task.Run(() => repo.MergePullRequests(pulls, false)); - - if (errors != null) - { - //Show any errors - for (var I = 0; I < errors.Count(); ++I) - { - var err = errors.ElementAt(I); - if (err != null) - MessageBox.Show(err, String.Format("Error merging PR #{0}", pulls[I].Number)); - } - return; - } - - if (pulls.Count > 0) - //regen the changelog - GenerateChangelog(repo); - - //Start the compile - var compileStarted = await Task.Run(() => currentInterface.Compiler.Compile(pulls.Count == 1)); - - if (error != null) - MessageBox.Show(String.Format("Error sychronizing repo: {0}", error)); - - if (!compileStarted) - MessageBox.Show("Could not start compilation!"); - else - MessageBox.Show("Test merges updated and compilation started!"); - } - finally - { - Enabled = true; - UseWaitCursor = false; - ApplyingPullRequestsLabel.Visible = false; - ApplyingPullRequestsProgressBar.Visible = false; - PullRequestListBox.Visible = true; - } - } - - /// - /// Called when the is clicked - /// - /// The sender of the event - /// The - void RefreshButton_Click(object sender, EventArgs e) - { - LoadPullRequests(); - } - - /// - /// Called when the is clicked. Adds the PR with the number in to or shows an error prompt if it doesn't/already exists - /// - /// The sender of the event - /// The - async void AddPRButton_Click(object sender, EventArgs e) - { - - Enabled = false; - UseWaitCursor = true; - try - { - IList pulls = null; - string error = null; - var mergedPullsTask = WrapServerOp(() => pulls = currentInterface.Repository.MergedPullRequests(out error)); - - int PRNumber; - try - { - PRNumber = Convert.ToInt32(AddPRNumericUpDown.Value); - } - catch - { - MessageBox.Show("Invalid PR number!"); - return; - } - var found = false; - var asString = PRNumber.ToString(); - foreach (var I in PullRequestListBox.Items) - if (((string)I).Split(' ')[0].Substring(1) == asString) - { - found = true; - break; - } - if (found) - { - MessageBox.Show("That PR is already in the list!"); - return; - } - - await mergedPullsTask; - if (pulls == null) - MessageBox.Show(String.Format(MergedPullsError, error)); - //get the PR in question - var PR = await client.Issue.Get(repoName, repoOwner, PRNumber); - if(PR == null ||PR.PullRequest == null) - { - MessageBox.Show("That doesn't seem to be a valid PR!"); - return; - } - InsertPullRequest(PR, true, CheckState.Unchecked); - } - finally - { - UseWaitCursor = false; - Enabled = true; - } - } - } -} diff --git a/legacy/V3/TGS.ControlPanel/TestMergeManager.resx b/legacy/V3/TGS.ControlPanel/TestMergeManager.resx deleted file mode 100644 index a83f34c04b..0000000000 --- a/legacy/V3/TGS.ControlPanel/TestMergeManager.resx +++ /dev/null @@ -1,2179 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAgIAAAAQAgAKgQAABGDgAAMDAAAAEA - IACoJQAA7h4AAKCgAAABACAAqJwBAJZEAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAA - AAAAAAAAAAAAAAAAAABeTTgAWkgyA1pIMilOOyMoRjMaRz4qEMM+KhDESDUcSU07IyhaSDIpWUgxA15N - NwAAAAAAAAAAAAAAAADSzsgAPyoQAEk2HTk9KQ7ZOSUK5DciB+43Igf/OCQJ/zkkCe46JQrkPSgO2UYz - Gjk8JwwA0s3HAAAAAABVQywAbV1JAk47IytALBK6XEo0/2xcSP9lVUD/mo+C/5CEdf9jUz7/ZFQ//2BP - Ov9LOCC6SzgfK2hYRAJSPygA////AUg1HHA7JgzkOSQJ/29gTP9wYU7/6+nm/+rn5f9rXEj/U0Ap/1NB - Kv9YRzD/Z1ZC/zsmC+RINBtw////AYFzYgU/KhCxOiUK/4V4aP+hl4r/ZFQ///b19P/a1tH/c2RR/87J - w//X083/Y1M+/3BhTv8+Kg//PyoQsYFzYgVYRi8cPSkOz0MvFf/a1tH/vreu/2RTPv/08/H//Pz7//Py - 8P/+/f3/+vn5/3JjUP+Hemr/RjMa/z0pDs9XRS8cQi4VnDciB/xsXEn/9PPx/7y1rP9kUz7/9PLx//// - //////////////f29f9qW0f/oJaJ/3BhTf83Igf8Qi4VnD8qEN45JAn/saie//////+7tKv/ZFM+//Ty - 8f//////7u3q/5SJev95a1n/Y1M9/9rX0v+zq6D/OSQJ/z8qEN5EMBeFNiEF/oV4aP//////vLSr/2RT - Pv/08vH//////+7t6v+UiXr/eWtZ/2NTPv/X0s3/h3pq/zYhBf5EMBeFRzMaZTYhBfuAc2L//////7y0 - q/9kUz7/9PLx//////////////////f29f9qWkX/rqaa/4R3Zv82IQX7RzMaZUUxGIc3Igf/X085/97b - 1/+9tqz/ZFM+//Tz8f/8/Pv/8/Lw//79/f/6+fn/cWJP/5KHeP9kUz7/NyIH/0UxGIdMOSE0PikPvzkk - Cf+Lf2//in5u/2JSPf/29fT/2tbR/3JkUf/PycP/19PN/2NTPv9oWEP/OycM/z0pD79LOCA0RDEYAE89 - JSU/KxHWcGBN/1NAKf9wYU3/9vX0/+rn5P9pWkX/VEIq/1NBKv9YRzD/ZlZB/zwnDdZPPSUlRTEYAGdY - QwAAAAAAQCwSkkw5If9cSjT/bF1J/6ujl/+lm4//j4N0/4R3Z/9lVUD/YE86/0IuFP8/KxGSAAAAAGdY - QwAAAAAAQy8WAFA+JiFDLxZmPCcMnzciBvM5JAn/NyIH/zgjCP87Jgz/OCMI8zwnDZ9DLxVmTzwlIUEs - EwAAAAAAAAAAAAAAAAAAAAAAQi4VAEYyGQc+KQ+GPSgO6EEtE61CLhStPSkP6D0pDoZEMBcHQS0TAAAA - AAAAAAAAAAAAAOAHAADgBwAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB - AADAAwAAwAMAAPAPAAAoAAAAGAAAADAAAAABACAAAAAAAAAJAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wD///8A////AP///wBVQy0AemxaA047I0NCLhTGQS0TxlE+J0aFd2cEYlI9AP// - /wD///8A////AP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF5NNwBhUDsMTToifk47 - I50+KQ+BQy8WmzwnDeg4Iwj/OCMI/z0oDupDLxacPikPgU47I51NOiF+YFA6DF1NNwAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAj4R1AB8HAABLOCBgOiUK+DYhBf82IQX/OCMI/zciB/82IQX/NiEF/zUg - Bf82IQX/NiEF/zYhBf86JQr4SzgfYB8HAACOgnMAAAAAAAAAAAAAAAAAAAAAAAAAAACMgXIACAAAAGBP - OS0/KxHVRzMa/2BPOf9eTTf/QSwT/0UyGf+Lf2//joJy/15NN/9fTjn/X085/11MNv9KNx//PysR1V9O - OC0GAAAAjIFyAAAAAAAAAAAAnpWIANrY0wFaSTIhRTIZdUAsEtc2IQb/d2lW/35xX/+Lf2//vLWs/8/K - xP/7+vr/wLqx/3NkUf9qWkb/alpG/25fS/+GeWj/ZFM+/z8qENdFMRh1WEYvIdTOyQGZjX8AbF1KAG1e - ShxFMhjMOSQJ/jciB/8zHgL/dmhV/1E/KP9oWET/+Pf2///////h3tr/UD4m/zsnDP9EMBb/QzAW/zsm - C/9BLRT/hnlp/0EtFP84Iwj+RTEYzG1dShxsXUkATDkgAE06Iig7JgzoNyIH/z4pD/9wYU3/ubKo/1RB - Kv9qWkb/+Pf3///////Hwbn/OyYL/4p+bv/c2NT/3NjT/6iek/87Jwz/e21b/1E+J/82IQb/OyYM6E06 - IihMOSAAWUcwAF1MNjs9KQ/0NSAE/2pbRv/z8vD/7evo/1JAKf9qWkb/+Pf3///////p5+T/tq6l/9vX - 0v///////////9XRy/9BLRP/in1t/25fS/81HwT/PSkP9F5NNztZRzEAAAAAAEo3H3A6JQr9NB8D/3xu - XP//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XRy/9BLBP/kIR1/35w - Xv80HwP/OiUK/Uo3HnAAAAAAUkApRj4pD+M3Igf/OiUK/6iekv//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/k4d5/6qhlf86JQr/NyIH/z4pD+NSPyhGQy8V1jgj - CP82IQb/emxa//Tz8f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yj - mP83Igf/mI6A//b29P97bVv/NiEG/zgjCP9DLxXWRTEYxDgjCP82IQb/m5CD////////////7Oro/1JA - Kf9qWkb/+Pf3/////////////////6ackP9XRS7/WEYw/0IuFf91Z1T/4d7Z//////+bkIP/NiEG/zgj - CP9FMRjEUT4nXTsmDPU2IAX/XUw2/+3r6f//////7Oro/1JAKf9qWkb/+Pf3/////////////////6ac - kP9XRS7/WEYv/0IuFP92Z1T/4t/b/+7s6v9dTDb/NiAF/zsmDPVRPiddYlE7Gz8qENQ3Igb/SDUc/93a - 1f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yjmP83Igf/mo+B/9/c - 2P9INRz/NyIG/z8qENRhUTwbX044Jj8qEOA2IAX/WUcx/+3r6f//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/kod4/+/t6/9ZRzD/NiAF/z8qEOBfTjgmVEIqRjwn - DfI3IQb/TDkg/8bAuf//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XR - y/9BLBP/kYV2/8nDvP9MOSH/NiEG/zwnDfJUQitGVUMsQj4pD+Q4Iwj/NiEG/047I//PysT/7uzq/1JA - Kf9qWkb/+Pf3///////p5+T/tq6l/9vX0v///////////9XRy/9CLhT/fG5c/1tKNP82IQX/OCMI/z4p - D+RVQyxCwry1Ak07I0Y/KhDUNyIH/0s4IP+akIL/jIBw/0UxGP9rW0f/+Pf3///////Fv7j/OiYL/4p+ - bv/c2NT/3NjT/6iek/87Jwz/eWpY/006Iv83Igf/PioQ1E06Ika8ta0CY1M+AEIvFQBYRjA1PCcN3lA9 - Jf96a1n/NyIH/zQeA/9qW0b/+Pf2///////e29b/TToi/zsmC/9DLxb/QzAW/zsmC/9BLRT/hnlp/0Et - FP89KA7eV0YvNUMwFgBhUDwAAAAAAG5fSwD5+PcBQCwSm0QwF/+FeGj/gnVk/3lrWf+Vinz/9fPy//// - ///39vX/ubGo/4F0Y/9yYk//alpG/25fS/+GeWj/Y1M9/zciB/9BLhSb+fr5AW5gTAAAAAAAAAAAAAAA - AABBLRMASjcfVjsmDPs7Jgv/Qy8W/047I/9aSDL/hHdm/6CWif9xYk7/cWJP/52Thv+DdWX/Xk04/11M - Nv9KNx//OCMH/zsmDPtKNx9WQS0TAAAAAAAAAAAAAAAAAAAAAABcSzUAY1I9E006ImxFMRieQCsSxzgj - CPU2IAX/NiEG/zYhBv81IAT/NSAE/zYhBv82IQb/NSAF/zciB/U+KhDHQy8Wnko3H2xiUTwTXEo0AAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGXiwDJxL0BalpGDT4qEHM5JQrzOCMI/zgjCP86Jgv7OyYL+zgj - CP84Iwj/OSQK8z4pD3NnV0MNt66lAZKGeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzggAFNBKgVBLRSBPysR60QwFrBNOyNpTzwlaUYyGbBALBLrQS0TgVA+JgVJNh0AAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA/4H/APgAHwD4AB8A8AAPAIAAAQCAAAEAgAABAIAAAQCAAAEAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAADAMAAAwDgAAcA4AAHAPgAHwD+AH8AKAAAACAA - AABAAAAAAQAgAAAAAAAAEAAAEwsAABMLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHlrWQCGeWkEVkQtRkUyGclEMBfJU0EpR52UhwZ7blwAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAalpHAAAAAABlVUAmcGBNQG5fTDE8Jw0dXEs1L0g1HIw9KQ7qOCMI/zgjCP8+KQ/sTToikltK - My88Jw0dbl9MMXBgTUBlVUAmAAAAAGdXQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABaSDIAY1I9IEQwF8pBLRP5PysR7TkkCeA+KQ/rOSUK/jgjCP84Iwj/OCMI/zgj - CP85JAr+PSkP6zkkCeA/KxHtQS0T+UQwF8piUTwgWUgxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAeGpXALy2rQJKNx+MOSQK/zchBv83Igb/NyIH/zgiB/84Iwj/NyIH/zUg - Bf81IAX/NyIH/zciB/83Igf/NyIH/zciBv83Igf/OSQK/0o3Hoy6s6oCdmhWAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAANnUzwA0HwMAXk44Rz4pD+05JQr/RTEY/0g1HP9GMhn/OSQJ/zUf - BP9AKxH/e21b/3xuXP9HMxr/RjIZ/0g0HP9INBv/RzQb/0EtFP83Igf/PSgO7V5NN0czHgIA2dTPAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ2ThgAWAAAAYlE8ElRCK2BFMRfVNiEG/1xLNf+pn5T/lIl7/6ee - kv+DdmX/bl9L/763rv/5+Pf/8vHv/66mm/+Wi33/lop8/5aKfP+Wi33/mo+C/4l9bf9HNBv/Qy8W1VRC - K2BhUDsSJxAAAI+CcwAAAAAAAAAAAAAAAAByY1AAg3ZlFVE/J2lALBLFOyYM+jgjCP81IAT/a1tH/4Z5 - af83Igf/e21b//X08//39vX///////v6+f+aj4L/STYd/z4pD/89KA7/PSgO/z0pDv9DLxb/dGZT/5WK - e/8+KQ//OyYL+j8rEcVQPSVpgHJhFW5eSwAAAAAAAAAAAEw6IgBgTzpiPioQ/jgjCP84Iwj/OCMI/zMe - Av9pWUT/g3Zl/zEcAP93aVb//Pv7////////////1tLN/0UyGf83Igf/Qi4U/0MvFv9DLxb/QCwS/zci - B/83IQb/kIR1/15NN/82IAX/OCMI/z4qEP5gTzliTDkhAAAAAAAAAAAAPCgNAEMvFWk5JAn/OCMI/zgj - CP86JQr/V0Uv/66lmv+Ie2v/MRwA/3dpVv/8+/v///////////+5sqn/NSAF/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cWJP/zUgBP84Iwj/OSQJ/0MvFmk8KA0AAAAAAAAAAAAAAAAAU0AphDkk - Cf84Iwj/OCIH/4x/cP/q5+T//////4p+bv8xHAD/d2lW//z7+////////////8fBuv9oWUT/fW9d/+7s - 6v////////////////+Wi3z/Mh0B/3VmU/+YjX//NyIH/zgjCP85JAn/U0EphAAAAAAAAAAAemlYAAAA - AABPPSWYOSQJ/zgjCP87Jgv/vbat////////////in1u/zEcAP93aVb//Pv7/////////////Pz8//j3 - 9v/5+Pj//v7+/////////////////5iMf/8yHQH/d2hW/7ixp/87Jgv/OCMI/zkkCf9PPSWYAAAAAHRm - UgBcSzUAZVQ/J0EtE9M4Iwj/OCMI/z0pDv/JxL3///////////+KfW7/MRwA/3dpVv/8+/v///////// - ////////////////////////////////////////mIx+/zIdAf93aFb/xb+3/z0pD/84Iwj/OCMI/0Et - E9NjUz4nW0kzAHlrWBBJNh2oOSQJ/zgjCP82IQb/U0Ap/+He2v///////////4p9bv8xHAD/d2lW//z7 - +/////////////////////////////////////////////////+YjH7/Mh0B/3VnVP/d2dX/U0Ep/zYh - Bv84Iwj/OSQJ/0g1HKh3aVcQTjsjjDsmC/k4Iwj/NyIH/0AsEv+zq6H/////////////////in1u/zEc - AP93aVb//Pv7/////////////////////////////////////////////////4t/b/8yHAD/c2RR//v7 - +v+0raP/QCwT/zciB/84Iwj/OyYL+U47I4xFMRjoOCMH/zgjCP82IAX/hHdm//r6+f////////////// - //+KfW7/MRwA/3dpVv/8+/v///////////////////////f29f/Szsj/zcjC/87Jw/+wqJ7/Tzwk/zUf - BP+MgHD///7+//v6+v+Ed2f/NiAF/zgjCP84Iwf/RTEY6E48JJs5JAn/OCMI/zUgBf97bVv/+vn5//// - /////////////4p9bv8xHAD/d2lW//z7+///////////////////////4d7Z/1A+Jv89KQ//PioQ/zkk - Cv89KQ//nJGE/+zq5///////+vn5/3ttW/81IAX/OCMI/zkkCf9OPCSbYlI9O0AsEuk4Iwf/NyIH/0Yy - Gf/Tzsj/////////////////in1u/zEcAP93aVb//Pv7///////////////////////h3tn/UD4m/z0p - D/8+KhD/OSQJ/z0pD/+ckYT/7Oro///////Tzsj/RjIZ/zciB/84Iwf/QCwS6WJSPTuooJQFRTEYoDgj - CP84Iwj/NiEG/6KYi/////////////////+KfW7/MRwA/3dpVv/8+/v///////////////////////f2 - 9f/Szsj/zcjC/87Jw/+wqJ3/Tzwk/zUfBP+MgHD//////6KYi/82IQX/OCMI/zgjCP9FMRigqJ+TBQAA - AABOOyOLOSQJ/zgjCP84Iwj/sKid/////////////////4p9bv8xHAD/d2lW//z7+/////////////// - //////////////////////////////////+Lf2//MhwA/3NkUf/+/v7/sKid/zgjCP84Iwj/OSQJ/048 - I4sAAAAAycO7BUk1Ha04Iwj/OCIH/0ArEf/MxsD/////////////////in1u/zEcAP93aVb//Pv7//// - /////////////////////////////////////////////5iMf/8yHQH/dGVS//79/f/Mx8D/QCsR/zgi - B/84Iwj/STYdrcjEvAV8b10UQzAWzDgjCP84Iwf/PikP/62kmf/9/Pz///////////+KfW7/MRwA/3dp - Vv/8+/v/////////////////////////////////////////////////mIx+/zIdAf90ZVL/+fj3/66m - m/8+KQ//OCMH/zgjCP9EMBbMfW9eFGVVQCZBLBPhNyIH/zgjCP83Igf/Qy8V/5yShf/18/L//////4p9 - bv8xHAD/d2lW//z7+/////////////z8/P/49/b/+fj4//7+/v////////////////+YjH//Mh0B/3Rl - Uv+nnZH/Qy8W/zciB/84Iwj/NyIH/0EsE+FlVUAmhHZmDE48JIE8Jw3zOCMI/zgjCP81IAX/VkQt/9bS - zf/+/v7/in1t/zEcAP93aVb//Pv7////////////x8G6/2hZRP99b13/7uzq/////////////////5aL - fP8yHQH/cWJO/3RlUv80HwP/OCMI/zgjCP88Jw3zTTsjgYF0YwxjUj0AfG5dBE88JV4/KxHkOCMI/zUg - BP91Z1T/kIV2/3NlUv9XRS//Mx4C/3dpVv/8+/v///////////+3sKb/NSAE/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cmNP/zUgBP84Iwj/PyoQ5E48JF58blwEY1I9AAAAAAByZVEA////AF5N - OEk+KhDmNR8E/3ZnVP9tXUn/LxkA/zUfBP80HgP/d2hW//z7+////////////9LNx/9CLhX/OCMI/0Iu - FP9DLxb/Qy8W/0AsEv83Igf/NyEG/5CEdf9eTTf/NiAF/z4qEOZdTTdJ////AHBhTgAAAAAAAAAAAAAA - AACBc2MAvreuA0UxGKM1IAX/cWJP/5qQgv9pWUX/Wkgy/0g1HP9/cWD//Pv7////////////+fj4/5SJ - ev9GMhn/OyYM/zwnDf89KA7/PSkO/0MvFv90ZlP/lYp8/z4qD/84Iwj/RTEYo764sAOBdGQAAAAAAAAA - AAAAAAAAAAAAAAAAAAA6JQoASzggXDkkCfxDLxb/bF1J/4ByYf+NgXL/lop8/62kmf/g3dn///////j3 - 9v/f29f/2NTP/8/KxP/CvLT/pZyQ/5WKfP+Wi33/mo+C/4l9bf9HNBv/NyIG/zolCvxLOCBcOiUKAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBdTDYkQCwS4jciB/81HwT/NR8E/zciB/88Jw3/QS0T/1pI - Mv+UiHr/cGBN/0s4H/9KNx//cGBN/5aLff9fTzn/RzMa/0c0G/9BLRT/NyIH/zciBv84Iwj/QCwS4l1M - NiRdTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi39wAI2AcgVWRC1lSDUcqEMvFs0/KxHqOiUK/Dgj - CP84Igf/NyEG/zUgBf81IAT/NyIH/zciB/81IAX/NSAF/zYhBv83Igf/NyIH/zolCvw+KhDqQS0UzUYy - GahUQiplkIR1BYyAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJuRhACzq6IDeGpYEWNT - PipCLhV5OiUK8jgjCP84Iwj/OCMI/zgjCP84Igf/OCIH/zgjCP84Iwj/OCMI/zgjCP85JAryQi4UeWFQ - OypvX0wRpJqOA4x/cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzgfAFVDKwRCLhRtOiYL8jgjCP84Iwj/OiUL+0EsE+dBLRPnOyYL+zgjCP84Iwj/OiUL8kEt - E21RPicESTUdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAU0EqAGBPOgRFMhl9QS0T7Ek2HblNOiJpZFQ/MGhYQzBRPidpSzkguUIu - FOxEMRd9XEw2BFE/KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+B///wAA//4A - AH/8AAA//AAAP/AAAA/AAAADwAAAA8AAAAPAAAADwAAAA4AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACAAAABAAAAAAAAAAAAAAAAAAAAAIAAAAHgAAAH4AAAB/AAAA/wAAAP8AAAD/wAAD//gAH//8AD/ygA - AAAwAAAAYAAAAAEAIAAAAAAAACQAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGxc - SACShXcEaVlFT0o3HstINBzLZFM+T4R3ZwRkUz4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACXjH4AAAAAAGdXQyNRPiaTQCsR6zkkCf85JAn/PyoQ6048JJR+cF8v////AIl8bAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1c/OAGtcSAB0ZVMib2BMU4F0 - Y1OMgHBJSTYdIT4pDx1rW0cmXUw2dEIuFNY6JQr+OCMH/zgjCP84Iwj/OCMI/zolC/9HMxrgW0kzdWtb - RyY+KQ8dSTYdIYyAcEmBdGNTb2BMU3RlUyJrXEgAysjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfW9eAIR3 - Zw1MOSGsQS0T/0UxGP5FMRj4OSQK4zolC+FBLRPmPSkO/DgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP83Igf/PSgO/EEtE+Y6JQvhOSQK40UxGPhFMRj+QS0T/0s5IKyEd2YNfW9dAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACgl4sAAAAAAGFQOmw9KA76NyIH/zciB/83Igf/OCMI/zgjCP84Iwf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjB/84Iwj/OCMI/zciB/83Igf/NyIH/z0oDvpgTzpsAAAAAKKY - jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABXRS4Aa1tHJEQxF9U4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zYhBf82IQX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9EMBfValpGJFZELQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCWiQDEvbUHVEIrmjolCv84Iwj/NiEF/zUgBP81IAT/NSAE/zUg - BP83Igf/OCMI/zgjCP82IQb/OSQJ/2BPOf9gUDr/OSQJ/zUgBP81IAT/NSAE/zUgBP81IAT/NSAE/zUg - BP81IAT/NiEG/zgjCP86JQr/VEEqmsG6sweek4cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl41+AODd2ARtXUleQi4U9DciB/85JAn/YE86/31v - Xf98b13/fG9d/3lrWf9KNx//NB8D/zYgBf9PPSX/n5WI/+zq5//t6+j/oZeK/21dSv94aVf/fG9d/3xv - Xf98b13/fG9d/3ttXP9wYU7/VUMs/zomC/82IQb/Qi4U9GxdSV7h3dkEl41+AAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AGtaRgCNgHAFY1I9OlNBKp9EMBbvOCMI/zgj - CP8+Kg//wbuy/7Wto/+il4v/oZeL/8nEvf+so5j/alpG/4l9bf/b19L//v79////////////7Orn/763 - rv+onpP/opiM/6OYjP+jmIz/opiM/6Sajv+wp53/w721/6ifk/9OOyP/NiEG/0QwFu9TQiqfY1M+Oo2A - cQVrW0cA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wANAAAAqKCUDlxKND9JNh2fQCwS7Tkk - Cf83Igf/OCMI/zgjCP8/KxH/wLqx/11MNv81IAT/NR8E/4+DdP/+/v7/+Pf2//v7+/////////////// - ///Qy8T/Y1I9/z0pD/84Iwj/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TToh/5qPgv+2rqT/RzQb/zYh - Bv85JAn/QCwS7Uc0G59ZRzE/p52RDh4IAAD08e8AAAAAAAAAAAAAAAAAAAAAAJ+WiQCjmo0YX085sj8r - EfM4Iwj/OCMH/zgjCP84Iwj/OCMI/zgjCP8/KxH/wLqx/15NN/82IAX/NSAF/4+DdP////////////// - /////////////+zq5/9jUj3/NB8D/zgjCP84Iwj/OCIH/zgjB/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsm - C/+upZr/in5u/zYgBf84Iwj/OCMH/zgjCP8/KxHzXkw3sqGXihiflIcAAAAAAAAAAAAAAAAAAAAAAIF0 - YwB/cmEtSDUc7TchBv84Iwj/OCMI/zgjCP84Iwj/OCMI/zciB/89KQ7/wLmw/15NN/82IAX/NSAF/4+D - dP///////////////////////////8C6sf88Jw3/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9sXEj/tKyi/zwoDv84Iwj/OCMI/zgjCP83IQb/SDUc7YByYS2CdGQAAAAAAAAA - AAAAAAAAAAAAAFZFLgBWRC0tPysR7TgjCP84Iwj/OCMI/zgjCP83Igf/NSAE/zwoDf9rXEj/1NDK/11M - Nv82IAX/NSAF/4+DdP///////////////////////////6KYi/82IQb/NyIG/0s4IP+6s6n/y8W+/8rE - vP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYhBv9RPyj/vLSr/0g0HP83Igf/OCMI/zgjCP84Iwj/PysR7VZE - LS1XRS4AAAAAAAAAAAAAAAAAAAAAAEEtEwBINBsxOSUK7zgjCP84Iwj/OCMI/zgjB/9ALBL/cGFO/7ix - p//y8O7/8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////5aKfP8yHAH/Mx0C/089 - Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9KNx//vreu/1NBKv83IQb/OCMI/zgj - CP84Iwj/OSUK70c0GzFBLRMAAAAAAAAAAAAAAAAAAAAAAGVUPwB+cF5eQCwS/jgiB/84Iwj/OCMI/zYg - Bf+GeWj/9/b1////////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ix - p/93aFb/d2lX/4p+bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9INBv/1tHM/4p9 - bf81IAX/OCMI/zgjCP84Igf/QCwS/n5wXl5lVD8AAAAAAAAAAAAAAAAAAAAAAFpJMwBsXEhpPysR/zgj - CP84Iwj/OCMI/zYhBv+glYj/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////7+/v/+/f3//v79//7+/v////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/3NjT/6GYi/82IQb/OCMI/zgjCP84Iwf/PysR/2xdSWlbSTMAAAAAAAAAAAAAAAAAn5WIAAAA - AABPPSV9OiYL/zgjCP84Iwj/OCMI/zgjCP+tpZr/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/3NjT/6+nnP84Iwj/OCMI/zgjCP84Iwj/OiYL/089JX0AAAAAn5SIAAAA - AAD///8AY1M+AG5fS0BFMRjnNyIH/zgjCP84Iwj/OCMI/zkkCf+8taz/////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+/uLD/PCgN/zciBv9HNBv/3dnU/764r/85JAn/OCMI/zgjCP84Iwj/NyIH/0Ux - GOdsXUlAYlE8AP///wB7bVsAgHJhH0s4IMY4Iwj/OCMI/zgjCP84Iwj/NyIH/0MvFf/OycP///////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////+/ubD/PCgN/zciBv9HNBv/3NjT/9HMxf9DLxX/NyIH/zgj - CP84Iwj/OCMI/zgjCP9LOCDGf3JgH3ttWwCimYwNUkAomjolC/84Iwj/OCMI/zgjCP84Iwj/OCMI/5CE - df/6+vn/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////++t67/PCcN/zciBv9HNBv/2tbQ//38 - /P+RhXb/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQv/UT8omqCWiQ1ZSDJ6PioQ9jgjB/84Iwj/OCMI/zgj - CP82IAX/bl9L/+3r6f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+qoZX/OCMI/zci - B/9HNBv/2dXQ///////u7Or/cWJO/zYgBf84Iwj/OCMI/zgjCP84Iwf/PioP9llHMXpKNx/tNyIH/zgj - CP84Iwj/OCMI/zYhBv9UQiv/19LN////////////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9PO - yP9fTjj/NiEG/zUgBP9PPCT/4t/c////////////2NTP/1RCK/82IQb/OCMI/zgjCP84Iwj/NyIH/0s3 - H+1XRS+1OiUK/zgjCP84Iwj/OCMI/zUgBP9uX0z/9/b1////////////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP///////////////////////////////////////////+Th3v91Z1T/aFhE/2lZ - Rf9pWUX/ZlVB/0o3Hv83Igf/OyYL/0s4IP+flYj//Pv7////////////9/b1/25fTP81IAT/OCMI/zgj - CP84Iwj/OiUK/1dGL7VrW0hMQCsS7zgjB/84Iwj/OCMI/zciB/9FMRj/zsnC//////////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////////////////////9vX - 0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c1//8+/v/////////////////zsnC/0Ux - GP83Igf/OCMI/zgjCP84Iwf/QCsR72tbR0yglooMUj8ouTgjCP84Iwj/OCMI/zgjCP81IAX/iX1t//7+ - /f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - /////////////9vX0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c2P/8+/v///////// - ///+/f3/iX1t/zUgBf84Iwj/OCMI/zgjCP84Iwj/Uj8ouaGXiwwbAwAAZVVAYD0pD/o4Iwj/OCMI/zgj - CP82IQb/UD4m/+Dd2P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - /////////////////////////////+Th3v91Z1T/aFhE/2lZRf9pWUX/ZlVB/0k2Hv83Igf/OyYL/0s4 - IP+glon//Pv7///////g3Nj/UD4m/zYhBv84Iwj/OCMI/zgjCP89KQ/6ZVVAYBsDAABtXUoAcWNPE0Et - FMY4Iwj/OCMI/zgjCP84Iwf/PSkP/8S+tv//////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9LO - yP9eTjj/NiEG/zUgBP9PPCT/4+Dc///////Evrb/PSkP/zgjCP84Iwj/OCMI/zgjCP9BLRTGcWJQE21d - SgCwp5wAxb63C1I/KL83Igf/OCMI/zgjCP83Igf/RzMa/9nW0P//////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+qoZX/OCMI/zciB/9HNBv/2dXQ///////Z1dD/RjMa/zciB/84Iwj/OCMI/zci - B/9SQCi/xsG4C7CpngByYk8AdmdVFUUxGNU4Iwj/OCMI/zgjCP82IQb/WEYw/+vp5v////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////++t6//PCcN/zciBv9HNBv/2dXQ///////r6eb/WEYv/zYh - Bv84Iwj/OCMI/zgjCP9FMhjVdmhVFXFjUAB+cF4Af3FgNkUxGPA3Igf/OCMI/zgjCP81IAT/b2BN//n5 - +P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////+/ubD/PCgN/zciBv9HNBv/2dXQ//// - ///5+fj/b2BN/zUgBP84Iwj/OCMI/zciB/9FMRjwf3JgNn5wXwBKNh4AXUw2Sj0oDvk4Iwj/OCMI/zgj - CP82IAX/bF1J/+7s6v//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/2dXQ///////v7ev/bV1J/zYgBf84Iwj/OCMI/zgjCP89KA75XUw2Sko3HgA6JgsAY1M+ejwn - Df84Iwj/OCMI/zgjCP84Iwj/OiUL/3lqWP/j4N3/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/2tbR/+bk4P96bFr/OyYL/zgjCP84Iwj/OCMI/zgjCP88Jw3/ZFM+ejsm - CwAAAAAATz0ljDkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zYhBv9hUDr/zsnC//7+/v//////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////7+/v/+/f3//v79//7+/v////////////// - //////////////////+/uLD/PCgN/zciBv9JNR3/x8G5/21dSf82IAX/OCMI/zgjCP84Iwj/OCMI/zgj - CP85JAn/Tz0ljAAAAAAAAAAAcGFOREo2HtQ4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP83Igf/emxa//Dv - 7f//////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ixp/93aFb/d2lX/4p+ - bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9JNh3/urKo/047I/83IQb/OCMI/zgj - CP84Iwj/OCMI/zgjCP9JNh3Ubl9LRAAAAADU0MsAAAAAAGFRPCROOyO3OiYL/TgjB/84Iwj/OCMI/zci - B/9KNh7/x8G5/8zGv//X087/2tbR/1hHMP82IQX/NSAF/4+DdP///////////////////////////5SJ - e/8yHAH/Mx0C/089Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9LNx//u7Oq/088 - JP83Igb/OCMI/zgjCP84Iwf/OiYL/U47I7dhUTwkAAAAANTQywAAAAAAxL61AEUxGQCAcmEUSzggiz8r - Efo4Iwj/OCMI/zciBv9PPSX/u7Oq/048JP9EMRf/UD4m/z4qEP84Iwj/NSAF/4+DdP////////////// - /////////////52Thv82IQb/NyIG/0s4IP+6s6n/y8W+/8rEvP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYh - Bv9RPyj/vLWs/0g1HP83Igf/OCMI/zgjCP8/KxH6Sjcfi31vXRRDLhUAxL61AAAAAAAAAAAAAAAAAAAA - AACFeGgAnJOFCmRUP3w+KhDzOCMI/zciBv9PPCX/urKp/0k2Hf82IQb/NiEG/zgjCP84Iwj/NSAF/4+D - dP///////////////////////////7qyqf86JQr/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9rXEj/tK2i/zwoDv84Iwj/OCMI/z4qEPNjUz58mY+BCoJ2ZQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAjIBwAJyShAhJNh61OCMI/zciBv9PPSX/urKp/0c0G/80HgP/NSAF/zYh - Bv83Igf/NSAE/4+DdP///////////////////////////+Xi3/9ZSDH/NB8E/zgjCP84Iwj/OCIH/zgj - B/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsmC/+tpZr/in5u/zYgBf84Iwj/OCMI/0o3HrWckoUIjIBwAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJGEACgRAABUQituOyYL/zciB/9KNh7/x8G5/6GX - iv+BdGP/bV5K/1hHMP9KNx7/OycM/5CEdf/////////////////////////////////Fv7f/XUw2/zwo - Df83Igf/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TDoh/5qPgf+2rqT/RzQb/zciB/84Iwj/OyYL/1RC - K24oEQAAnJKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBiUjwtQCsR6Dci - B/86JQr/ZVVA/4p+bv+imIz/same/722rf+/uK//uLGn/9XRy//5+Pj///////////////////////z8 - +//9/fz/7evo/8bAuP+so5j/p52S/6Sajv+imIz/opiM/6Sajv+wp5z/w721/6ifk/9OOyP/NiEG/zgj - CP84Iwj/QCsR6GJSPC1dTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHhq - VwCNgnIIRTIYszgjCP84Iwj/NiAF/zUgBP82IQb/OyYM/0MwFv9QPSb/ZFQ//3FiT/9+cF//4d3Z//f2 - 9f/c2NP/saie/31vXf98b13/rqWa/9vY0//5+Pf/4+Hd/4x/cP97blz/fG9d/3ttXP9wYU7/VUMs/zom - C/83Igb/OCMI/zgjCP84Iwj/RTIZs46Dcwh4a1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAMG5sAAvGQAAXEo0ejsmDP83Igf/NyIH/zgjCP84Iwj/OCMI/zciB/83IQb/NSAF/zUg - BP82IQX/YE85/21eSv9JNh3/OSQJ/zUgBP81IAT/OCMI/0g0HP9rW0f/YE86/zYgBf81HwT/NSAE/zUg - BP81IAT/NiEG/zgjCP84Iwj/NyIH/zciB/87Jgz/XEs1ei4YAADEvrQAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYRjAAbl5LLFNBKrZEMRfaQi4U9DwnDfw5JQr/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/NiEF/zUgBf83Igf/OCMI/zgjCP84Iwj/OCMI/zciB/81IAX/NiEF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf87Jgz8QCwS9EIuFdpRPie2bl9MLFhHMQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARTEYALGpngp6bFoaa1xIO2JS - PWRRPyiMTjsjvzolC/A4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQrwTjsjv088JIxdSzVkZFM+O3FiTxqil4sKNCAGAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAPb19AD///8BnpOGCEIuFWY7JgzwOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsmC/BBLRRmnZOGCP///wH29fQAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkMrAG5dSQJGMxlmPCcN7zgjCP84Iwj/OCMI/zgj - CP84Iwf/OCMI/zsmC/87Jgv/OCMI/zciB/84Iwj/OCMI/zgjCP84Iwj/PCcM70QwF2ZmVkMCUT8oAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFlHMABxYU4CSTYdZT0o - Du84Igf/OCMJ/zwnDP1AKxHmSTYdvV1MNoleTTiJTDkhvUEtE+Y8KA39OCQJ/zciB/88KA3vSDUcZWlb - RgJUQywAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AABjUj0Af3FiA007InRFMhjvTjsjw11NN3xlVUAyi39vC////wH///8BlIl7C21dSTJjUz58UT8ow0cz - Gu9MOCB0eGpWA19ONwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///gf//8AAP//8A///wAA//AAAA//AAD/4AAAB/8AAP/gAAAH/wAA/8AAAAP/ - AAD/gAAAAf8AAP8AAAAA/wAA/AAAAAA/AADwAAAAAA8AAOAAAAAABwAA4AAAAAAHAADgAAAAAAcAAOAA - AAAABwAA4AAAAAAHAADgAAAAAAcAAOAAAAAABwAAwAAAAAADAACAAAAAAAEAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA - AAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAMAAAAAAAwAA4AAAAAAH - AADwAAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAA/gAAAAB/AAD+AAAAAH8AAP8A - AAAA/wAA//AAAA//AAD//AAAP/8AAP/+AAB//wAA//8AAP//AAAoAAAAoAAAAEABAAABACAAAAAAAACQ - AQATCwAAEwsAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AHNjUf9zY1H/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Am5CD/z4pD/84Iwj/OCMI/zsnDP+NgXL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/UD0m/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0w4IP+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBvXkv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2dX - RP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI2Bcv87Jgv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/4FzYv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Atq2j/0k1Hf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/6eek/////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AY1Q//zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/X085/9DMxv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCNgXL/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/gXNi/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/9DLxf/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9AKxL/m5CD//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ApJuQ/3ttW/+Ie2r/kIN0/5eMfv+il4z/q6OW/7Ssov+9tq3/yMK6/9DMxv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AysS+/1hHMP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9YRzD/ycS9/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDQzMb/yMK6/722rf+0rKL/q6OW/6KXjP+XjH7/kIN0/4h7 - av97bVv/pJuQ/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9CLhT/Sjcf/1NA - KP9fTzn/Xk02/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/X085/1NAKP9KNx//Qi4U/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zolCv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGJRPf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKmg - lP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBDLxf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wB6alj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP96bFr/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDHwrn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/8O9tf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AU0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5eMfv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKq - oP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/sqqg//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBINRz/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wCDd2X/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OSQJ/1dGLv/Iwrr/yMK6/1dGLv84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/gnRk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS/5uQg/////////////// - ////////p56T/0IuFP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/MxsD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AWEcw/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/emxa////////////////////////////////////////////iHtq/zkk - Cf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8An5aI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZW - Qv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/dGRT/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/0MzG//// - ///////////////////////////////////////////////////QzMb/Xk02/zgjCP84Iwj/Qi4U/1xL - Nf9pWUX/dGRT/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3ps - Wv96bFr/emxa/3psWv9uYEz/ZlVB/048JP86JQr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Av7ev/z8qEP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - ////////////////////////////////////////////////////////RjMZ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf+tpJj///////////////////////////////////////// - ///////////////////////////////////KxL7///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////8/Lxf+hl4n/Y1M+/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PyoQ/7+3r/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wC0rKL/V0Yu/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////////////////////////////////////////////////// - /////////////////////////////3xuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/4l+ - bv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////iX5u/zsnDP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/V0Yu/7Ss - ov////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9OPCT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP/JxL3///////// - //+Ie2r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZlZC//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////8O9 - tf+2raP/qJ2S/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KX - jP+il4z/opeM/6Sajv+yqZ//wLmx/87Iwv//////////////////////////////////////altG/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk/6eek/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Am5CD/0c0G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy///////////////////////Szsj/ycS9/722 - rf+2raP/wryz//////////////////////////////////////////////////////////////////// - /////////////////////////////7Cnnf92Z1X/RjMZ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/1VDLP+Qg3T///////////////////////////+ajoH/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RzQb/52RhP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCQg3T/QS0T/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////pp2R/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r//////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/5CEdf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AI2Bcv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsnDP+kmo7///////// - ////////lIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PCgN/4FzYv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AgXNi/zol - Cv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////VUMs/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////////9mVUH/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/3Nl - Uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMnEvf9sXEj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ZlZC/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/21dSf/KxL7/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Awry0/2NT - Pv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////pp2R/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv////////////////9/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1xLNf+2raP/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AubOq/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////0c0 - G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/dGZU/////////////////zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7mxqP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AKmglP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9eTTb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////9lVUD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/k4d3////////////m5CD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AjIBx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////PCgN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1VELf///////////9HM - x/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AHtuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OSQJ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9/cmD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBvXkv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////6adkf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/PCgN/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/YlI8/0MvF/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8S+t////////////1dGLv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/b15L/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AX085/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+PgnT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - ////////////////////////////////////////////////////////////////////////wbux/1E+ - J/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+onZL///////// - //9wYU3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/19POf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFA+Jv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/q6OW//////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////gnRk/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPib/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBCLhT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1pIMf+2raP///////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////3ZnVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8jCuv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv///////////5CEdf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/7ato////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWkX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////UT4n/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Bc2L///////// - //+YjYD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9wYU3/zsjC//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OiUK/3xuXf////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2hXQ/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////7KqoP86JQr/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDPycL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9oV0P/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/PycL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AwLmx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/1tKNf////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////9bSTT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/wLmx/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKpn/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////aVpF/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7KqoP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCimY3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/empY//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4d7a/////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+GeWj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/lIp7/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIV3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+XjH7///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////lot8/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4Z5aP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB1aFT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pp2R//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////6SbkP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AZ1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Ssov////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+yqqD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEYzGf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/CvLT///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////wry0/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AG9eS/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0czH//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/altG/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJuQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+bkIP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AMzGwP86JQr/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/8jC - uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBMOCD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9JNR3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBsXEj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/RTEY//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////QzAW/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/2lZRf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCXjH7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1RC - Kv////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////1NAKP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/mI2A/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wDGwLj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+elIb///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////+elIb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf/GwLj/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ARzQb/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zkkCf9sXEj///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////25g - TP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZlZC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9KNx////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////TToi/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9iUT3/////AP///wD///8A////AP///wD///8A////AP///wD///8Ak4d3/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jwz/zsjC//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3dl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////9POyP88KA3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5KH - eP////8A////AP///wD///8A////AP///wD///8Aw762/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/opmN//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////opeM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wLmx/////wD///8A////AP// - /wD///8A////AEYzGf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cWJP//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9iUjz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////92Z1X/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0UxGP////8A////AP///wD///8A////AGNUP/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////1E+J/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/YVA7/////wD///8A////AI2Bcv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OycM/9DM - xv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////////////////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+PgnT/////AMK8 - tP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////1pIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3tuXP///////////////////////////////////////////////////////////6ad - kf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7+3r/9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////4l8 - bP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+QhHX///////// - ////////////////////////////////////////////////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9TQCj/lYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1A9Jv////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3tuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wryz//////////////////////////////////// - //////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/l4x+//// - /wA7Jwz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //+5s6r/hnlo/0ArEv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/UT4n//////////////////////////////////////////////////////////////////// - /////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/////wD///8Aemxa/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/w721//////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/8nEvf////////////// - /////////////////////////////////////////////////////////////8K8tP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3ttW/////8A////AM7Iwv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29eS/////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QCsS/7u1rP////////////////////////////////////////////// - //////////////////////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Py8X/////AP// - /wD///8AYU86/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9HNBv/hnlo//// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/////AP///wD///8A////ALSsov84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4p+cP////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/XEs1/722rf////////////////////////////////////////////// - ////////////////////////////////////////////////////////iX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/tKyi/////wD///8A////AP///wD///8ASTYe/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - /////////////////////////////0EtE/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////wD///8A////AP// - /wD///8A////AJiNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3///////////////////////// - /////////////////////////////////////////////////////////////////////////////6Sb - kP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5qOgf////8A////AP///wD///8A////AP///wD///8AOycM/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9QPib///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/fG5d//////////////////////////////////////////////////// - //////////////////////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwo - Df////8A////AP///wD///8A////AP///wD///8A////AHxuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/8G7 - sf////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - ///////////////////AubH/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3/////AP///wD///8A////AP// - /wD///8A////AP///wDSzsj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9sXEj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/vbat//////////////////// - ////////////////////////////////////////////////////////////////////////aVpF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/087I/////wD///8A////AP///wD///8A////AP///wD///8A////AGJS - PP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/4d7a/////////////////////////////// - /////////////////////////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wC2r6T/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+He2v///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/722rf////////////////////////////////////////////// - /////////////4Z6af84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7avpP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEo3H/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP87Jwz/ysS+//////////////////////////////////////////////////////9AKxL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0k2Hv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCbkIP/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/4J0ZP8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1NAKP////////////// - //////////////////////////////////+il4z/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+bkIP/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/bmBM//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3RmVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wry0//////////////////////////////////// - ////////bmBM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP88KA3/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/5GGdv///////////////////////////////////////////4yAcf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/f3Fh/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+vp5z///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP97blz///////// - //////////////////////////////////+tpJj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/0MzG//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubOq/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////zsjC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDMxsD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/zcjB/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Aq6GV/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0w4IP////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////Y1Q//zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////Sjcf/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6ujlv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIl8bP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Jfm7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBoV0P/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/joRz//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////+NgXL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ARzQb/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Cnnf////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////r6ec/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/RzMf///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMrEvv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////OSQJ/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8rGv/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/TToi//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////0w4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+poJT/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiHtq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29e - S/////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - //////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/h3tr/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGZVQf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Qg3T///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////joRz/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBGMxn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////6idkv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////722rf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AycS9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5SKe/////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////////////////////6KXjP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP/KxL7/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cGFN//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////3VoVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qaCU//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCGemn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9UQir///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////1hH - MP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4h7av////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0Iu - FP/CvLP///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////x8C4/0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9mVUH/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEUx - GP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/6KXjP////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////p56T/zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QzAW//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fnFf//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////q6OW/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP// - /wD///8A////AP///wDIwrr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9fTjf///////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/yMK6/////wD///8A////AP///wD///8A////AP///wD///8A////AHxu - Xf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2 - Hv/Py8X//////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fG5d//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Ap56T/zolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////Z1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDEvrf/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9RPif/hnlo/3xuXf////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2VVQP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9DLxf/xL63/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBVRC3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9QPSb///////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9nV0T/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9VRC3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AHNj - Uf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9zY1H/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Alot8/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////8G7sf+7s6v///////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2dXRP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9QPSb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4FzYv///////////5iNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Wi3z/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wC7s6v/PyoQ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/RjMZ/2JSPP98bl3/mI2A/7Ssov/OyML//////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWUX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///JxL3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //+QhHX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/z4pD/+2raP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBPPCX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////dGZU/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/088 - Jf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AGpb - Rv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////4J0ZP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////CvLT/U0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/6idkv///////////3BhTf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZVQf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+OhHP/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwoDf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/2JRPf9FMRj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Dvrb///////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4l+bv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCvp5z/PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OyYL/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/6mglP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AM/Lxf9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VUMs////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/STYe/8/Lxf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFVE - Lf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////Qi4U/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5GGdv///////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////39yYP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9fTzn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6mglP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////OyML/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9zZVL/////////////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBiUT3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////3Bh - Tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP89KQ7/////////////////gXNi/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9jVD//////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ApJqO/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////Qy8X/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/pp2R/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pJuQ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8rEvv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//// - /////////////2ZWQv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AX043/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Sh3j///////// - ////////wry0/6adkf+JfGz/bV1J/1A9Jv85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////z8nC/0k2Hv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/opeM/////////////////5aLfP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/19ON/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJ+WiP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZVVA//////////////////////////////////// - ///////////////////AubH/pJqO/4Z6af9qW0b/TToi/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////hnlo/zsm - C/84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b//////////////////////6ujlv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+PgnT///////////////////////////////////////////////////////// - /////////////////////////////722rf+gl4v/g3hm/2hXQ/9JNh7/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////npSG/2lZRf8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUMs/4+CdP///////////////////////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP8+KQ//W0k0/3doVf+Uinv/sKme/87Iwv////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////AubH/sqqg/6adkf+il4z/opeM/6KX - jP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+imY3/sKme/7+3 - r//OyML//////////////////////////////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Wkgx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0EtE/9eTTb/emxa/5eMfv+0rKL/0czH//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////jIBx/zwoDf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5mQgv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2FPOv98bl3/mZCC/7ewpv////////////////////////////// - //+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/6KZjf9lVUD/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBUQir/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/Y1M+/3VoVP9INRz/OCMI/6ujlv////////////// - /////////////////////////////////////////////////////////////62kmP9xYk//PyoQ/z8q - EP9xYk//raSY//////////////////////////////////////////////////////////////////// - ////////sKme/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/bmBM/2ZV - Qf9OPCT/OyYL/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9UQir/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AlIh5/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////// - //////////////////+il4z/ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/WEcw/5SK - e//Py8X/////////////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/lot8/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA5JAn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+elIb////////////////////////////CvLT/iHtq/006Iv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0c0G/96bFr/tq2j//// - ////////////////////////pJqO/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8ATzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/52R - hP//////r6ec/3NlUv89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/ZlVB/6KXjP//////opmN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/048JP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI+C - dP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+Qg3T/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0MzG//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCKfnD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNR3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2ZVQf+NgXL/tq2j/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9DLxf/ZlVB/42B - cv+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/0MwFv9mVUH/jYFy/7ato/////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDAubH/mI2A/3Bh - Tf9CLhT/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/3BhTf+YjYD/wLmx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/STYe/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8ATToi/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBRPif/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFNA - KP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tzwl//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AVEIq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/UD0m/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBYRzD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VEIq/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AW0k0/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/V0Yu/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBcTDb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/QCsS/3ZnVf+wqZ7/sKme/3ZnVf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEw2//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGFQO/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2Hv+Bc2L/vbat/////wD///8A////AP// - /wD///8A////AMnEvf+NgXL/Tzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEs1/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AYlE9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zkk - Cf9fTzn/m5CD/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Ap56T/21dSf87Jgv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBjVD//OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD/c2VS/6+nnP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AL22 - rf+Bc2L/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/UD4m/4h7 - av/CvLT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDPy8X/lIp7/1dG - Lv84Iwj/OCMI/zgjCP84Iwj/ZlVB/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AbV9K/19POf+bkIP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9tXUn/bFtH//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - //////////5/////////////////////////+B/////////////////////////gB/////////////// - /////////8AD////////////////////////AAD///////////////////////wAAD////////////// - ////////+AAAD//////////////////////gAAAH/////////////////////4AAAAH///////////// - ////wAf+AAAAAH/gA//////////////AAAAAAAAAAAAD/////////////4AAAAAAAAAAAAH///////// - ////AAAAAAAAAAAAAP////////////8AAAAAAAAAAAAA/////////////gAAAAAAAAAAAAB///////// - ///8AAAAAAAAAAAAAD////////////wAAAAAAAAAAAAAP///////////+AAAAAAAAAAAAAAf//////// - ///4AAAAAAAAAAAAAB////////////AAAAAAAAAAAAAAD///////////4AAAAAAAAAAAAAAH//////// - ///gAAAAAAAAAAAAAAf//////////8AAAAAAAAAAAAAAA///////////gAAAAAAAAAAAAAAB//////// - //+AAAAAAAAAAAAAAAH//////////wAAAAAAAAAAAAAAAP/////////+AAAAAAAAAAAAAAAAf/////// - //gAAAAAAAAAAAAAAAAf////////4AAAAAAAAAAAAAAAAAf///////+AAAAAAAAAAAAAAAAAAf////// - /gAAAAAAAAAAAAAAAAAAf//////4AAAAAAAAAAAAAAAAAAAf/////+AAAAAAAAAAAAAAAAAAAAf///// - AAAAAAAAAAAAAAAAAAAAAP////wAAAAAAAAAAAAAAAAAAAAAP///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///4AAAAAAAAAAAAAAAAAAAAAAH///A - AAAAAAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAB//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAD+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAA4AA - AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAYAA - AAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAA+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAD/AA - AAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH//AAAAAAAAAAAAAAAAAAAAAAAA//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/wA - AAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAAAAAAAB///A - AAAAAAAAAAAAAAAAAAAAAAP//+AAAAAAAAAAAAAAAAAAAAAAB///8AAAAAAAAAAAAAAAAAAAAAAP///8 - AAAAAAAAAAAAAAAAAAAAAD////4AAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAAAAAAAAAAAAAD///// - gAAAAAAAAAAAAAAAAAAAAf/////AAAAAAAAAAAAAAAAAAAAD//////AAAAAAAAAAAAAAAAAAAA////// - 8AAAAAAAAAAAAAAAAAAAD//////4AAAAAAAAAAAAAAAAAAAf//////gAAAAAAAAAAAAAAAAAAB////// - +AAAAAAAAAAAAAAAAAAAH//////8AAAAAAAAAAAAAAAAAAA///////wAAAAAAAAAAAAAAAAAAD////// - /AAAAAAAAAAAAAAAAAAAP//////+AAAAAAAAAAAAAAAAAAB///////4AAAAAAAAAAAAAAAAAAH////// - /gAAAAAAAAAAAAAAAAAAf///////AAAAAAAAAAAAAAAAAAD///////8AAAAAAAAAAAAAAAAAAP////// - /wAAAAAAAAAAAAAAAAAA////////gAAAAAAAAAAAAAAAAAH///////+AAAAAAAAAAAAAAAAAAf////// - /4AAAAAAAAAAAAAAAAAB////////gAAAAAAAAAAAAAAAAAH////////AAAAAAAAAAAAAAAAAA/////// - /8AAAAAAAAAAAAAAAAAD////////8AAAAAAAAAAAAAAAAA//////////gAAAAAAAAAAAAAAB//////// - ///8AAAAAAAAAAAAAD/////////////gAAAAAAAAAAAH//////////////4AAAAAAAAAAH////////// - /////wAAAAAAAAAA////////////////gAAAAAAAAAH////////////////AAAAAAAAAA/////////// - /////+AAAAAAAAAH////////////////8AAAAAAAAA/////////////////4AAAAAAAAH/////////// - //////wAAAAAAAA//////////////////gAAAAAAAH//////////////////AAAH4AAA//////////// - //////+AAH/+AAH//////////////////8AD///AA///////////////////4B////gH//////////// - ///////x/////4////////// - - - \ No newline at end of file diff --git a/legacy/V3/TGS.ControlPanel/packages.config b/legacy/V3/TGS.ControlPanel/packages.config deleted file mode 100644 index 346d66fcc6..0000000000 --- a/legacy/V3/TGS.ControlPanel/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/legacy/V3/TGS.ControlPanel/tgs.ico b/legacy/V3/TGS.ControlPanel/tgs.ico deleted file mode 100644 index 6ed69fadfcdf8deb049f66dfffc46d5361332feb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123198 zcmeHQ2b>f|_HG1(QxxyiJH1mu&xnDbAQ>bqf<%dugM>v;q9P!>AR-bJ0TGd8IK_aX z2#5hz#f;%jFx}~`r~lK_Logl)QvdI(nVPNX?V0K7shQnbrhdPk?y0V>diCn-SFc{Z zs&<@mPDSUKV;r2TJ0mJMP7lX%PB zX4{+n+bBCg5VM}LHT8#F$viu1R(UX^)tZ+V_Gx?kJFQ0BSK&->jo zecN3mM)kc^4DH@rEW2%nSTJ*<7}B+=xUyR_F|boZaasN9&s zh;pEBOrtZae1USrH3Qp=F@rjaZ~pUd@%5MgC$2(XU7MdHx?WO4^lDQ}4DZoGG&=Lh zFD||4PhcZ@>PM_~5;_BpuAV zX^pCa&+yq^IW>AHEX@4(t~{e7{E={PkDy%f5YL+>p-V z=E)-kcp&&p&(<|hYH`8w%fY7uqx!a$&pfx`(a`hXe+Tl4!vB8$NsR2%Myy_P3+mTF z@EJY}IzDB+pIEa}CVuv*N1~qpcGuTp@hy|Z^;ca1`kRQ4Klqzi|Hx{#3G47_t6G1! zsotp-53=qI&&7SdsrcY;Z^=6F89v+Q!aokh_5S?vgFT;@k@%7H_iim7dtjNEdEGF< zXZURE+9%vhoDAsHKz#c555)5u!}a&z-`K&O#KM~=$@=jbKHI**sTVapr|MSLXKvm# zV$QUy#o)`E2-SY$(0=b|?Kh%VOVQ}8D&T+Bt-#Vnt!kg(bZTA6OsOvHqVc1Fuzh z{sHB#0?vkYYJA42mz;O3ELZ;l9-N#yCsqPKX5n4?>XP%1U5oP8qOM;7Z?n)|l@O;t z;uKbLLOVhJ*Zn|!G1+-^WT0Wa7P(}^v+^9;2hNn$Ko-4qU>EQ3(;Ne%~DG%`I zYs4+*J*Mf<@YHDoJ2$M-&n4g7W!*M&qEiig1AYcVZtMk?iD#K7+f4nhTRjx7{xdCP z*FZjd>x}X4Y~}xX)5kfLaW$}0gY$utop?te_ZkS)N1)9`>pCY&oZmWQoQxaLv4MPd z`n9ho+Fe|Y*xEUuWBv0FM;^>eg_U>Ac0T^_y>h+V);SeqD&JjGW9&nkMb^QqW?m=#R0PIhM>^pVG+pm|qYkr^7X0JG9XQ?-YgR6j@LeZ@SP_Z4kwIpARtbg^iE>Mf@GIRE3>?eG_yaE9TrVTJoxHwF;CmKcd|l zUD2ktYzDN?7+~Pni>j3uQ?KqTAN}vw#q(Er>Svih+MoU5v@!h!^I%@>Ep=EXdjH~P37$j0w4Mw{S$5U&($PDp=L{=`4< zdHYS*iq~Iy4tbm^`OiGNwKz8idR`0Q^9$9+khfCSke7bnFZP{$Z~8z-RPvU2GVd3k z+i)0k)SOToRHB|JmMxee=H-u}o*=G*?xf_y%Uhoo4Iyv5K?ikO)I8n+*6N~8pLT9?hL{YUitorpr<9lSLdmyJ|Ng$rgE9^Mi>G`;-}7nD zoSJnZ`_NA{mhu5I*4elBf93EV-i^Lm>Z3CzUMY3AJLXIkKmG8%WI1uO`H3}R`h+23 z9CRXGK2ygdO_yA7+}-WBS5Sfd~#}4j@qmx*% zXqLEf;!yN6^(EffztsP8C1QR*^Kk1DETivbb)6xXH#ru38wI|vY<}+VUPYVB_m1x) z56V{_MgJ?3=AXdtSHZ89$a~b_E{%_cE~UsWcE0D%xz6j`o-c>GABE?}W6bdbvBd*z z%ew7X{~Mfs#1Fv9_~AWU9<}MQzm!`zYf`cNVnqN)4#o}@(Kk&;-}(*ut98IxE_AS5 z=vwR0R^NaR)6t(*#P}yxHjbDsoV`vpdF*kjI{DC++(*oZwHpOad51@#Q%N4o^PrO_ z$9vV0t~ri~9)*FFB{pip4h{KIQ7Zai2B0 zIkI;fnH=N3Gogn!zj{FX+IY`S2A*m{p3Z~*`n76LwB>(+uZ4ZuU09Q8n3m7*S!Oc4 zXA6^}Wm@2$xUG)*y@B@Jhqm4YJ*E(4eFJPARAs9(Fm(|9`!`I(w0wro0=LyMMm8#c z(#W39a~szl2Hej>zJfBCvY+p9eICFxuc=S+;C1v7O3P=V56pXJ{lkY{JAzh(#NneI z?>NvUocnY|6wQP0oq$Uv}!TNtW(5;&|z-8cBs^+IIf0l z6yJZh8)L}3#ge%RFTlmi=qGaX#|=9E>i(BSSkb=WY0&Q*4u;%{!WH%9+$&niI@dnQk-GVT{re-` z?E4sdQ~xJz>m);mSK^xRY8{l9J()JwO&S|A`>#f2w_^nLanqdE zw87H(d6FNhe8{Vrs_e<6FUxHgjAoEyB$KXBlH9K-C}yH^}McrcQl z7fa{m$?~ro(L+4_#DjADp~`1jEOf%K%VqyKv|F>YATQoiyeEFpmqqf6RemIa$Hmfl z(<0@+ymb@D9+9?XSeKc@=lZL99RvKXQFZ4#NBbDZWGW<6K6UUtK89)cABkIZ1sJ;E-cz+I`e} z$TC@W$Hu4Mt$0saP8mbnpdJ#H&oK_i&unA7-+0H4kk`v^pNa9wW;_x3Gf0PZ( zQ`M1Wb!`0Sxh(tjm!7TIw(iMm!g~49KbSXcscnHP2lFPfZ1ppJ{168^U3t8h_mTh9 z88}|gy`r@oi&Fp08#^$neD=GX%b;9=&PF@DhyFEH&d-oW=Dp?Vb>*i|xKb4xb%xGE z8*X2`p9iQ*!Oke4tBLv3$4R}C??vG!A$tt0q5u2^_9u?Zsf+6R zCv|t^eSmqhj93A7DB2uPhiv>8vC4Mf!E4qH{kG)A;@Oi0=O$n~mNs5qvrNvJP#4$r zZJmAl93{^pak!8E?|b0!dg%K#t{Kq&xA!l*wfwxi#N|c^&@RAZr+dqDDlh zXn4kv`yoTVM4NmGT_R#zK>YWJ8x0#^1;}+Rr{I^cH*kDV0lNMT$os#L-J3Dq&BYvO zEzDolLRq<&HK@KBH2$|$?LXYmw|%_|u(A6pQ0%S7*%NWlaqhSRF%U<@PH}qjb_!${H(*ZLS$NZR6(C4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Installer.UI/FodyWeavers.xml b/legacy/V3/TGS.Installer.UI/FodyWeavers.xml deleted file mode 100644 index c6e1b7c8af..0000000000 --- a/legacy/V3/TGS.Installer.UI/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Installer.UI/Main.Designer.cs b/legacy/V3/TGS.Installer.UI/Main.Designer.cs deleted file mode 100644 index 0ed1cf9dec..0000000000 --- a/legacy/V3/TGS.Installer.UI/Main.Designer.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Diagnostics; -using System.Reflection; - -namespace TGS.Installer.UI -{ - partial class Main - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - CleanTempDir(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main)); - this.ProgressBar = new System.Windows.Forms.ProgressBar(); - this.DeskShortcutsCheckbox = new System.Windows.Forms.CheckBox(); - this.StartShortcutsCheckbox = new System.Windows.Forms.CheckBox(); - this.InstallingLabel = new System.Windows.Forms.Label(); - this.PathTextBox = new System.Windows.Forms.TextBox(); - this.SelectPathButton = new System.Windows.Forms.Button(); - this.label1 = new System.Windows.Forms.Label(); - this.VersionLabel = new System.Windows.Forms.Label(); - this.InstallButton = new System.Windows.Forms.Button(); - this.ShowLogCheckbox = new System.Windows.Forms.CheckBox(); - this.TargetVersionLabel = new System.Windows.Forms.Label(); - this.InstallCancelButton = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // ProgressBar - // - this.ProgressBar.Location = new System.Drawing.Point(11, 119); - this.ProgressBar.Name = "ProgressBar"; - this.ProgressBar.Size = new System.Drawing.Size(386, 22); - this.ProgressBar.TabIndex = 0; - // - // DeskShortcutsCheckbox - // - this.DeskShortcutsCheckbox.AutoSize = true; - this.DeskShortcutsCheckbox.Checked = true; - this.DeskShortcutsCheckbox.CheckState = System.Windows.Forms.CheckState.Checked; - this.DeskShortcutsCheckbox.Font = new System.Drawing.Font("Verdana", 12F); - this.DeskShortcutsCheckbox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.DeskShortcutsCheckbox.Location = new System.Drawing.Point(12, 61); - this.DeskShortcutsCheckbox.Name = "DeskShortcutsCheckbox"; - this.DeskShortcutsCheckbox.Size = new System.Drawing.Size(214, 22); - this.DeskShortcutsCheckbox.TabIndex = 2; - this.DeskShortcutsCheckbox.Text = "Add Desktop Shortcuts"; - this.DeskShortcutsCheckbox.UseVisualStyleBackColor = true; - // - // StartShortcutsCheckbox - // - this.StartShortcutsCheckbox.AutoSize = true; - this.StartShortcutsCheckbox.Checked = true; - this.StartShortcutsCheckbox.CheckState = System.Windows.Forms.CheckState.Checked; - this.StartShortcutsCheckbox.Font = new System.Drawing.Font("Verdana", 12F); - this.StartShortcutsCheckbox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.StartShortcutsCheckbox.Location = new System.Drawing.Point(289, 61); - this.StartShortcutsCheckbox.Name = "StartShortcutsCheckbox"; - this.StartShortcutsCheckbox.Size = new System.Drawing.Size(236, 22); - this.StartShortcutsCheckbox.TabIndex = 3; - this.StartShortcutsCheckbox.Text = "Add Start Menu Shortcuts"; - this.StartShortcutsCheckbox.UseVisualStyleBackColor = true; - // - // InstallingLabel - // - this.InstallingLabel.AutoSize = true; - this.InstallingLabel.Font = new System.Drawing.Font("Verdana", 12F); - this.InstallingLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.InstallingLabel.Location = new System.Drawing.Point(8, 37); - this.InstallingLabel.Name = "InstallingLabel"; - this.InstallingLabel.Size = new System.Drawing.Size(52, 18); - this.InstallingLabel.TabIndex = 1; - this.InstallingLabel.Text = "Path:"; - // - // PathTextBox - // - this.PathTextBox.Location = new System.Drawing.Point(66, 35); - this.PathTextBox.Name = "PathTextBox"; - this.PathTextBox.ReadOnly = true; - this.PathTextBox.Size = new System.Drawing.Size(413, 20); - this.PathTextBox.TabIndex = 4; - // - // SelectPathButton - // - this.SelectPathButton.Location = new System.Drawing.Point(485, 35); - this.SelectPathButton.Name = "SelectPathButton"; - this.SelectPathButton.Size = new System.Drawing.Size(40, 20); - this.SelectPathButton.TabIndex = 6; - this.SelectPathButton.Text = "..."; - this.SelectPathButton.UseVisualStyleBackColor = true; - this.SelectPathButton.Click += new System.EventHandler(this.SelectPathButton_Click); - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Verdana", 12F); - this.label1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.label1.Location = new System.Drawing.Point(8, 9); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(155, 18); - this.label1.TabIndex = 7; - this.label1.Text = "Detected Version:"; - // - // VersionLabel - // - this.VersionLabel.AutoSize = true; - this.VersionLabel.Font = new System.Drawing.Font("Verdana", 12F); - this.VersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.VersionLabel.Location = new System.Drawing.Point(169, 9); - this.VersionLabel.Name = "VersionLabel"; - this.VersionLabel.Size = new System.Drawing.Size(228, 18); - this.VersionLabel.TabIndex = 8; - this.VersionLabel.Text = "Unknown (No service running)"; - // - // InstallButton - // - this.InstallButton.Location = new System.Drawing.Point(400, 89); - this.InstallButton.Name = "InstallButton"; - this.InstallButton.Size = new System.Drawing.Size(125, 22); - this.InstallButton.TabIndex = 9; - this.InstallButton.Text = "Install"; - this.InstallButton.UseVisualStyleBackColor = true; - this.InstallButton.Click += new System.EventHandler(this.InstallButton_Click); - // - // ShowLogCheckbox - // - this.ShowLogCheckbox.AutoSize = true; - this.ShowLogCheckbox.Font = new System.Drawing.Font("Verdana", 12F); - this.ShowLogCheckbox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.ShowLogCheckbox.Location = new System.Drawing.Point(289, 90); - this.ShowLogCheckbox.Name = "ShowLogCheckbox"; - this.ShowLogCheckbox.Size = new System.Drawing.Size(105, 22); - this.ShowLogCheckbox.TabIndex = 10; - this.ShowLogCheckbox.Text = "Show Log"; - this.ShowLogCheckbox.UseVisualStyleBackColor = true; - // - // TargetVersionLabel - // - this.TargetVersionLabel.AutoSize = true; - this.TargetVersionLabel.Font = new System.Drawing.Font("Verdana", 12F); - this.TargetVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); - this.TargetVersionLabel.Location = new System.Drawing.Point(8, 90); - this.TargetVersionLabel.Name = "TargetVersionLabel"; - this.TargetVersionLabel.Size = new System.Drawing.Size(132, 18); - this.TargetVersionLabel.TabIndex = 11; - this.TargetVersionLabel.Text = "Target Version: v" + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; - // - // InstallCancelButton - // - this.InstallCancelButton.Enabled = false; - this.InstallCancelButton.Location = new System.Drawing.Point(400, 119); - this.InstallCancelButton.Name = "InstallCancelButton"; - this.InstallCancelButton.Size = new System.Drawing.Size(125, 22); - this.InstallCancelButton.TabIndex = 12; - this.InstallCancelButton.Text = "Cancel"; - this.InstallCancelButton.UseVisualStyleBackColor = true; - this.InstallCancelButton.Click += new System.EventHandler(this.CancelButton_Click); - // - // Main - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); - this.ClientSize = new System.Drawing.Size(537, 153); - this.FormClosing += Main_FormClosing; - this.Controls.Add(this.InstallCancelButton); - this.Controls.Add(this.TargetVersionLabel); - this.Controls.Add(this.ShowLogCheckbox); - this.Controls.Add(this.InstallButton); - this.Controls.Add(this.VersionLabel); - this.Controls.Add(this.label1); - this.Controls.Add(this.SelectPathButton); - this.Controls.Add(this.PathTextBox); - this.Controls.Add(this.StartShortcutsCheckbox); - this.Controls.Add(this.DeskShortcutsCheckbox); - this.Controls.Add(this.InstallingLabel); - this.Controls.Add(this.ProgressBar); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "Main"; - this.Text = "/tg/station Server Installer"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.ProgressBar ProgressBar; - private System.Windows.Forms.CheckBox DeskShortcutsCheckbox; - private System.Windows.Forms.CheckBox StartShortcutsCheckbox; - private System.Windows.Forms.Label InstallingLabel; - private System.Windows.Forms.TextBox PathTextBox; - private System.Windows.Forms.Button SelectPathButton; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.Label VersionLabel; - private System.Windows.Forms.Button InstallButton; - private System.Windows.Forms.CheckBox ShowLogCheckbox; - private System.Windows.Forms.Label TargetVersionLabel; - private System.Windows.Forms.Button InstallCancelButton; - } -} - diff --git a/legacy/V3/TGS.Installer.UI/Main.cs b/legacy/V3/TGS.Installer.UI/Main.cs deleted file mode 100644 index 955283ed10..0000000000 --- a/legacy/V3/TGS.Installer.UI/Main.cs +++ /dev/null @@ -1,461 +0,0 @@ -using Microsoft.Deployment.WindowsInstaller; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.ServiceProcess; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using TGS.Interface; -using TGS.Interface.Components; -using TGS.Server; - -namespace TGS.Installer.UI -{ - partial class Main : Form - { - const string DefaultInstallDir = "TG Station Server"; //keep this in sync with the msi installer - - string tempDir; - bool installing = false; - bool cancelled = false; - bool pathIsDefault = true; - /// - /// If we should attempt to make a for the new install - /// - bool attemptNetSettingsMigration = false; - - IClient Interface; - - /// - /// Construct an installer form - /// - public Main() - { - InitializeComponent(); - SetupTempDir(); - CheckForExistingVersion(); - PathTextBox.Text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), DefaultInstallDir); - } - - void SetupTempDir() - { - tempDir = Path.Combine(Path.GetTempPath(), "TGS3InstallerTempDir"); - try - { - if (File.Exists(tempDir)) - File.Delete(tempDir); - else if (Directory.Exists(tempDir)) - Directory.Delete(tempDir, true); - } - catch { } - if (File.Exists(tempDir) || Directory.Exists(tempDir)) - { - tempDir = Path.GetTempFileName(); - File.Delete(tempDir); //we want a dir not a file - } - try - { - Directory.CreateDirectory(tempDir); - } - catch - { - tempDir = null; - } - } - - void CleanTempDir() { - if(tempDir != null) - try - { - Directory.Delete(tempDir, true); - } - catch { } - } - - void CheckForExistingVersion() { - Interface = new Client(); - var verifiedConnection = Interface.ConnectionStatus().HasFlag(ConnectivityLevel.Administrator); - try - { - var realVersion = Interface.Server.Version; - var isV0 = realVersion < new Version(3, 1, 0, 0); - if (isV0) //OH GOD!!!! - MessageBox.Show("Upgrading from version 3.0 may trigger a bug that can delete /config and /data. IT IS STRONGLY RECCOMMENDED THAT YOU BACKUP THESE FOLDERS BEFORE UPDATING!", "Warning"); - var isUnderV2 = isV0 || realVersion < new Version(3, 2, 0, 0); - if (isUnderV2) - //Friendly reminger - MessageBox.Show("Upgrading to service version 3.2 will break the 3.1 DMAPI. It is recommended you update your game to the 3.2 API before updating the servive to avoid having to trigger hard restarts.", "Note"); - attemptNetSettingsMigration = isUnderV2; - } - catch - { - if (verifiedConnection) - VersionLabel.Text = "< v3.0.85.0 (Missing ITGService.Version())"; - } - } - - bool ConfirmDangerousUpgrade() - { - return MessageBox.Show("Unable connect to service! Existing DreamDaemon instances will be terminated. Continue?", "Warning", MessageBoxButtons.YesNo) == DialogResult.Yes; - } - - bool TellServiceWereComingForThem() - { - var connectionVerified = Interface.ConnectionStatus().HasFlag(ConnectivityLevel.Administrator); - try - { - Interface.Server.Management.PrepareForUpdate(); - Thread.Sleep(3000); //chat messages - return true; - } - catch - { - return ConfirmDangerousUpgrade(); - } - } - - private void Main_FormClosing(object sender, FormClosingEventArgs e) - { - e.Cancel = installing; //not allowed, nununu - } - - enum PKillType - { - Killed, - Aborted, - NoneFound, - } - - PKillType PromptKillProcesses(string name) - { - var processes = new List(Process.GetProcessesByName(name)); - processes.RemoveAll(x => x.HasExited); - if(processes.Count > 0) - { - if (MessageBox.Show(String.Format("Found {1} running instance{2} of {0}.exe! Shall I terminate {3}?", name, processes.Count, processes.Count > 1 ? "s" : "", processes.Count > 1 ? "them" : "it"), "Warning", MessageBoxButtons.YesNo) != DialogResult.Yes) - return PKillType.Aborted; - foreach (var P in processes) - { - if (!P.HasExited) - { - P.Kill(); - P.WaitForExit(); - } - P.Dispose(); - } - return PKillType.Killed; - } - return PKillType.NoneFound; - } - static string TextPrompt(string caption, string text) - { - Form prompt = new Form() - { - Width = 500, - Height = 150, - FormBorderStyle = FormBorderStyle.FixedDialog, - Text = caption, - StartPosition = FormStartPosition.CenterScreen, - MaximizeBox = false, - MinimizeBox = false, - }; - Label textLabel = new Label() { Left = 50, Top = 20, Text = text, AutoSize = true }; - TextBox textBox = new TextBox() { Left = 50, Top = 50, Width = 400 }; - Button confirmation = new Button() { Text = "Ok", Left = 350, Width = 100, Top = 70, DialogResult = DialogResult.OK }; - confirmation.Click += (sender, e) => { prompt.Close(); }; - prompt.Controls.Add(textBox); - prompt.Controls.Add(confirmation); - prompt.Controls.Add(textLabel); - prompt.AcceptButton = confirmation; - - return prompt.ShowDialog() == DialogResult.OK ? textBox.Text : null; - } - - bool HandleNoConfigMigration(ServerConfig sc) - { - var path = @"C:\TGSTATION-SERVER-3"; - if (Directory.Exists(path)) { - sc.InstancePaths.Add(path); - new InstanceConfig(path).Save(); - return MessageBox.Show(String.Format("Unable to migrate settings, default config created at {0}. You will need to reconfigure your server instance. Continue with installation?", path), "Migration Error", MessageBoxButtons.YesNo) == DialogResult.Yes; - } - return MessageBox.Show("All config migrations have failed. You will need to fully recreate your server. Continue with installation?", "Migration Failure", MessageBoxButtons.YesNo) == DialogResult.Yes; - } - - /// - /// Migrate to the new since the won't know about it until it's upgraded - /// - bool AttemptMigrationOfNetSettings() - { - if (!attemptNetSettingsMigration) - return true; - var sc = new ServerConfig(); - try - { - sc.PythonPath = Interface.Server.Management.PythonPath(); - } - catch { } - try - { - sc.RemoteAccessPort = Interface.Server.Management.RemoteAccessPort(); - } - catch { } - try - { - foreach (var I in Interface.Server.Instances) - sc.InstancePaths.Add(I.Metadata.Path); - } - catch { } - - try - { - if (sc.InstancePaths.Count > 0) //nice suprise, this is uneeded - return true; - - //stopping the service here is MANDATORY to get the correct reattach values - using (var controller = new ServiceController("TG Station Server")) - { - controller.Stop(); - controller.WaitForStatus(ServiceControllerStatus.Stopped); - //we need to find the old user.config - //check wow64 first - var path = @"C:\Windows\SysWOW64\config\systemprofile\AppData\Local\TGServerService"; - if (!Directory.Exists(path)) - { - //ok... check the System32 path - path = @"C:\Windows\System32\config\systemprofile\AppData\Local\TGServerService"; - if (!Directory.Exists(path)) - { - //well, i'm out of ideas, just use the default location - var res = HandleNoConfigMigration(sc); - if(!res) - { - controller.Start(); - return false; - } - } - } - - //now who knows wtf windows calls the damn folder - //take our best guess based on last modified time - DirectoryInfo lastModified = null; - foreach (var D in new DirectoryInfo(path).GetDirectories()) - if (lastModified == null || D.LastWriteTime > lastModified.LastWriteTime) - lastModified = D; - - if (lastModified == null) - { - //well, i'm out of ideas, just use the default location - var res = HandleNoConfigMigration(sc); - if (!res) - { - controller.Start(); - return false; - } - } - - var next = lastModified; - lastModified = null; - foreach (var D in next.GetDirectories()) - if (lastModified == null || D.LastWriteTime > lastModified.LastWriteTime) - lastModified = D; - - if (lastModified == null) - { - //well, i'm out of ideas, just use the default location - var res = HandleNoConfigMigration(sc); - if (!res) - { - controller.Start(); - return false; - } - } - - path = Path.Combine(tempDir, "user.config"); - var netConfigPath = Path.Combine(lastModified.FullName, "user.config"); - File.Copy(netConfigPath, path, true); - new InstanceConfig(tempDir).Save(); - - var instanceConfigPath = Path.Combine(tempDir, "Instance.json"); - - if (MessageBox.Show(String.Format("The 3.2 settings migration is a manual process, please open \"{0}\" with your favorite text editor and copy the values under TGServerService.Properties.Settings to the relevent fields at \"{1}\". If something in the original config appears wrong to you, correct it in the new config, but do not modify the \"Version\", \"Enabled\", or \"Name\" fields at all. See field mappings here: https://github.com/tgstation/tgstation-server/blob/a372b22fd3367dd60ee0cbebd9210f4b072c952d/TGServerService/DeprecatedInstanceConfig.cs#L23-L39", path, instanceConfigPath), "Manual Migration Required", MessageBoxButtons.OKCancel) != DialogResult.OK) - { - controller.Start(); - return false; - } - - var name = TextPrompt("Set Instance Directory", String.Format("Please enter the ServerDirectory entry from the original config here.{0}Use backslashes and uppercase letters. Leave this blank if it is not present.", Environment.NewLine)); - if (name == null) - { - controller.Start(); - return false; - } - - if (String.IsNullOrWhiteSpace(name)) - name = @"C:\TGSTATION-SERVER-3"; - - sc.InstancePaths.Add(name); - - if (MessageBox.Show(String.Format("Please confirm you have finished copying settings from \"{0}\" to \"{1}\"!", path, instanceConfigPath), "Last Confirmation", MessageBoxButtons.OKCancel) != DialogResult.OK) - { - controller.Start(); - return false; - } - //validate it for good measure - try - { - InstanceConfig.Load(tempDir); - } - catch (Exception e) - { - MessageBox.Show(String.Format("JSON Validation Error: {0}", e.Message)); - controller.Start(); - return false; - } - File.Copy(instanceConfigPath, Path.Combine(name, "Instance.json"), true); - return true; - } - } - finally - { - Directory.CreateDirectory(Server.Server.MigrationConfigDirectory); - sc.Save(Server.Server.MigrationConfigDirectory); - } - } - - async void DoInstall() - { - string logfile = null; - try - { - while (true) - { - var res = PromptKillProcesses("TGCommandLine"); - if (res == PKillType.Aborted) - return; - else if (res == PKillType.Killed) - continue; - res = PromptKillProcesses("TGControlPanel"); - if (res == PKillType.Aborted) - return; - else if (res == PKillType.Killed) - continue; - break; - } - - if (!TellServiceWereComingForThem()) - return; - - if (!AttemptMigrationOfNetSettings()) - return; - - var args = new List(); - if (!pathIsDefault) - args.Add(String.Format("INSTALLFOLDER=\"{0}\"", PathTextBox.Text)); - if (StartShortcutsCheckbox.Checked) - args.Add("INSTALLSHORTCUTSTART=1"); - if (DeskShortcutsCheckbox.Checked) - args.Add("INSTALLSHORTCUTDESK=1"); - - args.Add("REBOOT=R"); - - SelectPathButton.Enabled = false; - PathTextBox.Enabled = false; - DeskShortcutsCheckbox.Enabled = false; - StartShortcutsCheckbox.Enabled = false; - InstallButton.Enabled = false; - ShowLogCheckbox.Enabled = false; - InstallButton.Text = "Installing..."; - - var msipath = Path.Combine(tempDir, "TGS.Installer.msi"); - File.WriteAllBytes(msipath, Properties.Resources.TGSInstaller); - File.WriteAllBytes(Path.Combine(tempDir, "cab1.cab"), Properties.Resources.cab1); - - ProgressBar.Style = ProgressBarStyle.Marquee; - InstallCancelButton.Enabled = true; - - if (ShowLogCheckbox.Checked) - { - logfile = Path.Combine(tempDir, "tgsinstall.log"); - Microsoft.Deployment.WindowsInstaller.Installer.EnableLog(InstallLogModes.Verbose | InstallLogModes.PropertyDump, logfile); - } - var cl = String.Join(" ", args); - - // TODO: Uncomment this when OnUIUpdate is more robust - // Microsoft.Deployment.WindowsInstaller.Installer.SetInternalUI(InstallUIOptions.Silent); - Microsoft.Deployment.WindowsInstaller.Installer.SetExternalUI(OnUIUpdate, InstallLogModes.Progress); - - await Task.Run(() => Microsoft.Deployment.WindowsInstaller.Installer.InstallProduct(msipath, cl)); - - if (cancelled) - { - MessageBox.Show("Operation cancelled!"); - return; - } - } - catch (Exception ex) - { - MessageBox.Show("Error: " + ex.ToString()); - return; - } - finally - { - InstallCancelButton.Enabled = false; - ShowLogCheckbox.Enabled = true; - SelectPathButton.Enabled = true; - PathTextBox.Enabled = true; - DeskShortcutsCheckbox.Enabled = true; - StartShortcutsCheckbox.Enabled = true; - ProgressBar.Style = ProgressBarStyle.Blocks; - InstallButton.Enabled = true; - InstallButton.Text = "Install"; - installing = false; - cancelled = false; - if (ShowLogCheckbox.Checked && logfile != null) - try - { - Process.Start(logfile).WaitForInputIdle(); - } - catch { } - } - MessageBox.Show("Success!"); - Application.Exit(); - } - - MessageResult OnUIUpdate(InstallMessage messageType, string message, MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton) - { - if (cancelled && installing) - { - installing = false; - return MessageResult.Cancel; - } - return MessageResult.OK; - } - - private void InstallButton_Click(object sender, EventArgs e) - { - DoInstall(); - } - - private void SelectPathButton_Click(object sender, EventArgs e) - { - var fbd = new FolderBrowserDialog() - { - Description = "Select where you would like to install the service executables. This isn't where an actual server instance is created.", - ShowNewFolderButton = true - }; - if (fbd.ShowDialog() != DialogResult.OK) - return; - pathIsDefault = false; - PathTextBox.Text = fbd.SelectedPath + Path.DirectorySeparatorChar + DefaultInstallDir; - } - - private void CancelButton_Click(object sender, EventArgs e) - { - cancelled = true; - InstallCancelButton.Enabled = false; - } - } -} diff --git a/legacy/V3/TGS.Installer.UI/Main.resx b/legacy/V3/TGS.Installer.UI/Main.resx deleted file mode 100644 index a83f34c04b..0000000000 --- a/legacy/V3/TGS.Installer.UI/Main.resx +++ /dev/null @@ -1,2179 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAgIAAAAQAgAKgQAABGDgAAMDAAAAEA - IACoJQAA7h4AAKCgAAABACAAqJwBAJZEAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAA - AAAAAAAAAAAAAAAAAABeTTgAWkgyA1pIMilOOyMoRjMaRz4qEMM+KhDESDUcSU07IyhaSDIpWUgxA15N - NwAAAAAAAAAAAAAAAADSzsgAPyoQAEk2HTk9KQ7ZOSUK5DciB+43Igf/OCQJ/zkkCe46JQrkPSgO2UYz - Gjk8JwwA0s3HAAAAAABVQywAbV1JAk47IytALBK6XEo0/2xcSP9lVUD/mo+C/5CEdf9jUz7/ZFQ//2BP - Ov9LOCC6SzgfK2hYRAJSPygA////AUg1HHA7JgzkOSQJ/29gTP9wYU7/6+nm/+rn5f9rXEj/U0Ap/1NB - Kv9YRzD/Z1ZC/zsmC+RINBtw////AYFzYgU/KhCxOiUK/4V4aP+hl4r/ZFQ///b19P/a1tH/c2RR/87J - w//X083/Y1M+/3BhTv8+Kg//PyoQsYFzYgVYRi8cPSkOz0MvFf/a1tH/vreu/2RTPv/08/H//Pz7//Py - 8P/+/f3/+vn5/3JjUP+Hemr/RjMa/z0pDs9XRS8cQi4VnDciB/xsXEn/9PPx/7y1rP9kUz7/9PLx//// - //////////////f29f9qW0f/oJaJ/3BhTf83Igf8Qi4VnD8qEN45JAn/saie//////+7tKv/ZFM+//Ty - 8f//////7u3q/5SJev95a1n/Y1M9/9rX0v+zq6D/OSQJ/z8qEN5EMBeFNiEF/oV4aP//////vLSr/2RT - Pv/08vH//////+7t6v+UiXr/eWtZ/2NTPv/X0s3/h3pq/zYhBf5EMBeFRzMaZTYhBfuAc2L//////7y0 - q/9kUz7/9PLx//////////////////f29f9qWkX/rqaa/4R3Zv82IQX7RzMaZUUxGIc3Igf/X085/97b - 1/+9tqz/ZFM+//Tz8f/8/Pv/8/Lw//79/f/6+fn/cWJP/5KHeP9kUz7/NyIH/0UxGIdMOSE0PikPvzkk - Cf+Lf2//in5u/2JSPf/29fT/2tbR/3JkUf/PycP/19PN/2NTPv9oWEP/OycM/z0pD79LOCA0RDEYAE89 - JSU/KxHWcGBN/1NAKf9wYU3/9vX0/+rn5P9pWkX/VEIq/1NBKv9YRzD/ZlZB/zwnDdZPPSUlRTEYAGdY - QwAAAAAAQCwSkkw5If9cSjT/bF1J/6ujl/+lm4//j4N0/4R3Z/9lVUD/YE86/0IuFP8/KxGSAAAAAGdY - QwAAAAAAQy8WAFA+JiFDLxZmPCcMnzciBvM5JAn/NyIH/zgjCP87Jgz/OCMI8zwnDZ9DLxVmTzwlIUEs - EwAAAAAAAAAAAAAAAAAAAAAAQi4VAEYyGQc+KQ+GPSgO6EEtE61CLhStPSkP6D0pDoZEMBcHQS0TAAAA - AAAAAAAAAAAAAOAHAADgBwAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB - AADAAwAAwAMAAPAPAAAoAAAAGAAAADAAAAABACAAAAAAAAAJAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wD///8A////AP///wBVQy0AemxaA047I0NCLhTGQS0TxlE+J0aFd2cEYlI9AP// - /wD///8A////AP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF5NNwBhUDsMTToifk47 - I50+KQ+BQy8WmzwnDeg4Iwj/OCMI/z0oDupDLxacPikPgU47I51NOiF+YFA6DF1NNwAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAj4R1AB8HAABLOCBgOiUK+DYhBf82IQX/OCMI/zciB/82IQX/NiEF/zUg - Bf82IQX/NiEF/zYhBf86JQr4SzgfYB8HAACOgnMAAAAAAAAAAAAAAAAAAAAAAAAAAACMgXIACAAAAGBP - OS0/KxHVRzMa/2BPOf9eTTf/QSwT/0UyGf+Lf2//joJy/15NN/9fTjn/X085/11MNv9KNx//PysR1V9O - OC0GAAAAjIFyAAAAAAAAAAAAnpWIANrY0wFaSTIhRTIZdUAsEtc2IQb/d2lW/35xX/+Lf2//vLWs/8/K - xP/7+vr/wLqx/3NkUf9qWkb/alpG/25fS/+GeWj/ZFM+/z8qENdFMRh1WEYvIdTOyQGZjX8AbF1KAG1e - ShxFMhjMOSQJ/jciB/8zHgL/dmhV/1E/KP9oWET/+Pf2///////h3tr/UD4m/zsnDP9EMBb/QzAW/zsm - C/9BLRT/hnlp/0EtFP84Iwj+RTEYzG1dShxsXUkATDkgAE06Iig7JgzoNyIH/z4pD/9wYU3/ubKo/1RB - Kv9qWkb/+Pf3///////Hwbn/OyYL/4p+bv/c2NT/3NjT/6iek/87Jwz/e21b/1E+J/82IQb/OyYM6E06 - IihMOSAAWUcwAF1MNjs9KQ/0NSAE/2pbRv/z8vD/7evo/1JAKf9qWkb/+Pf3///////p5+T/tq6l/9vX - 0v///////////9XRy/9BLRP/in1t/25fS/81HwT/PSkP9F5NNztZRzEAAAAAAEo3H3A6JQr9NB8D/3xu - XP//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XRy/9BLBP/kIR1/35w - Xv80HwP/OiUK/Uo3HnAAAAAAUkApRj4pD+M3Igf/OiUK/6iekv//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/k4d5/6qhlf86JQr/NyIH/z4pD+NSPyhGQy8V1jgj - CP82IQb/emxa//Tz8f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yj - mP83Igf/mI6A//b29P97bVv/NiEG/zgjCP9DLxXWRTEYxDgjCP82IQb/m5CD////////////7Oro/1JA - Kf9qWkb/+Pf3/////////////////6ackP9XRS7/WEYw/0IuFf91Z1T/4d7Z//////+bkIP/NiEG/zgj - CP9FMRjEUT4nXTsmDPU2IAX/XUw2/+3r6f//////7Oro/1JAKf9qWkb/+Pf3/////////////////6ac - kP9XRS7/WEYv/0IuFP92Z1T/4t/b/+7s6v9dTDb/NiAF/zsmDPVRPiddYlE7Gz8qENQ3Igb/SDUc/93a - 1f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yjmP83Igf/mo+B/9/c - 2P9INRz/NyIG/z8qENRhUTwbX044Jj8qEOA2IAX/WUcx/+3r6f//////7Oro/1JAKf9qWkb/+Pf3//// - /////////////////////////////9XQy/9ALBL/kod4/+/t6/9ZRzD/NiAF/z8qEOBfTjgmVEIqRjwn - DfI3IQb/TDkg/8bAuf//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XR - y/9BLBP/kYV2/8nDvP9MOSH/NiEG/zwnDfJUQitGVUMsQj4pD+Q4Iwj/NiEG/047I//PysT/7uzq/1JA - Kf9qWkb/+Pf3///////p5+T/tq6l/9vX0v///////////9XRy/9CLhT/fG5c/1tKNP82IQX/OCMI/z4p - D+RVQyxCwry1Ak07I0Y/KhDUNyIH/0s4IP+akIL/jIBw/0UxGP9rW0f/+Pf3///////Fv7j/OiYL/4p+ - bv/c2NT/3NjT/6iek/87Jwz/eWpY/006Iv83Igf/PioQ1E06Ika8ta0CY1M+AEIvFQBYRjA1PCcN3lA9 - Jf96a1n/NyIH/zQeA/9qW0b/+Pf2///////e29b/TToi/zsmC/9DLxb/QzAW/zsmC/9BLRT/hnlp/0Et - FP89KA7eV0YvNUMwFgBhUDwAAAAAAG5fSwD5+PcBQCwSm0QwF/+FeGj/gnVk/3lrWf+Vinz/9fPy//// - ///39vX/ubGo/4F0Y/9yYk//alpG/25fS/+GeWj/Y1M9/zciB/9BLhSb+fr5AW5gTAAAAAAAAAAAAAAA - AABBLRMASjcfVjsmDPs7Jgv/Qy8W/047I/9aSDL/hHdm/6CWif9xYk7/cWJP/52Thv+DdWX/Xk04/11M - Nv9KNx//OCMH/zsmDPtKNx9WQS0TAAAAAAAAAAAAAAAAAAAAAABcSzUAY1I9E006ImxFMRieQCsSxzgj - CPU2IAX/NiEG/zYhBv81IAT/NSAE/zYhBv82IQb/NSAF/zciB/U+KhDHQy8Wnko3H2xiUTwTXEo0AAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGXiwDJxL0BalpGDT4qEHM5JQrzOCMI/zgjCP86Jgv7OyYL+zgj - CP84Iwj/OSQK8z4pD3NnV0MNt66lAZKGeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzggAFNBKgVBLRSBPysR60QwFrBNOyNpTzwlaUYyGbBALBLrQS0TgVA+JgVJNh0AAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA/4H/APgAHwD4AB8A8AAPAIAAAQCAAAEAgAABAIAAAQCAAAEAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAADAMAAAwDgAAcA4AAHAPgAHwD+AH8AKAAAACAA - AABAAAAAAQAgAAAAAAAAEAAAEwsAABMLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHlrWQCGeWkEVkQtRkUyGclEMBfJU0EpR52UhwZ7blwAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAalpHAAAAAABlVUAmcGBNQG5fTDE8Jw0dXEs1L0g1HIw9KQ7qOCMI/zgjCP8+KQ/sTToikltK - My88Jw0dbl9MMXBgTUBlVUAmAAAAAGdXQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABaSDIAY1I9IEQwF8pBLRP5PysR7TkkCeA+KQ/rOSUK/jgjCP84Iwj/OCMI/zgj - CP85JAr+PSkP6zkkCeA/KxHtQS0T+UQwF8piUTwgWUgxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAeGpXALy2rQJKNx+MOSQK/zchBv83Igb/NyIH/zgiB/84Iwj/NyIH/zUg - Bf81IAX/NyIH/zciB/83Igf/NyIH/zciBv83Igf/OSQK/0o3Hoy6s6oCdmhWAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAANnUzwA0HwMAXk44Rz4pD+05JQr/RTEY/0g1HP9GMhn/OSQJ/zUf - BP9AKxH/e21b/3xuXP9HMxr/RjIZ/0g0HP9INBv/RzQb/0EtFP83Igf/PSgO7V5NN0czHgIA2dTPAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ2ThgAWAAAAYlE8ElRCK2BFMRfVNiEG/1xLNf+pn5T/lIl7/6ee - kv+DdmX/bl9L/763rv/5+Pf/8vHv/66mm/+Wi33/lop8/5aKfP+Wi33/mo+C/4l9bf9HNBv/Qy8W1VRC - K2BhUDsSJxAAAI+CcwAAAAAAAAAAAAAAAAByY1AAg3ZlFVE/J2lALBLFOyYM+jgjCP81IAT/a1tH/4Z5 - af83Igf/e21b//X08//39vX///////v6+f+aj4L/STYd/z4pD/89KA7/PSgO/z0pDv9DLxb/dGZT/5WK - e/8+KQ//OyYL+j8rEcVQPSVpgHJhFW5eSwAAAAAAAAAAAEw6IgBgTzpiPioQ/jgjCP84Iwj/OCMI/zMe - Av9pWUT/g3Zl/zEcAP93aVb//Pv7////////////1tLN/0UyGf83Igf/Qi4U/0MvFv9DLxb/QCwS/zci - B/83IQb/kIR1/15NN/82IAX/OCMI/z4qEP5gTzliTDkhAAAAAAAAAAAAPCgNAEMvFWk5JAn/OCMI/zgj - CP86JQr/V0Uv/66lmv+Ie2v/MRwA/3dpVv/8+/v///////////+5sqn/NSAF/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cWJP/zUgBP84Iwj/OSQJ/0MvFmk8KA0AAAAAAAAAAAAAAAAAU0AphDkk - Cf84Iwj/OCIH/4x/cP/q5+T//////4p+bv8xHAD/d2lW//z7+////////////8fBuv9oWUT/fW9d/+7s - 6v////////////////+Wi3z/Mh0B/3VmU/+YjX//NyIH/zgjCP85JAn/U0EphAAAAAAAAAAAemlYAAAA - AABPPSWYOSQJ/zgjCP87Jgv/vbat////////////in1u/zEcAP93aVb//Pv7/////////////Pz8//j3 - 9v/5+Pj//v7+/////////////////5iMf/8yHQH/d2hW/7ixp/87Jgv/OCMI/zkkCf9PPSWYAAAAAHRm - UgBcSzUAZVQ/J0EtE9M4Iwj/OCMI/z0pDv/JxL3///////////+KfW7/MRwA/3dpVv/8+/v///////// - ////////////////////////////////////////mIx+/zIdAf93aFb/xb+3/z0pD/84Iwj/OCMI/0Et - E9NjUz4nW0kzAHlrWBBJNh2oOSQJ/zgjCP82IQb/U0Ap/+He2v///////////4p9bv8xHAD/d2lW//z7 - +/////////////////////////////////////////////////+YjH7/Mh0B/3VnVP/d2dX/U0Ep/zYh - Bv84Iwj/OSQJ/0g1HKh3aVcQTjsjjDsmC/k4Iwj/NyIH/0AsEv+zq6H/////////////////in1u/zEc - AP93aVb//Pv7/////////////////////////////////////////////////4t/b/8yHAD/c2RR//v7 - +v+0raP/QCwT/zciB/84Iwj/OyYL+U47I4xFMRjoOCMH/zgjCP82IAX/hHdm//r6+f////////////// - //+KfW7/MRwA/3dpVv/8+/v///////////////////////f29f/Szsj/zcjC/87Jw/+wqJ7/Tzwk/zUf - BP+MgHD///7+//v6+v+Ed2f/NiAF/zgjCP84Iwf/RTEY6E48JJs5JAn/OCMI/zUgBf97bVv/+vn5//// - /////////////4p9bv8xHAD/d2lW//z7+///////////////////////4d7Z/1A+Jv89KQ//PioQ/zkk - Cv89KQ//nJGE/+zq5///////+vn5/3ttW/81IAX/OCMI/zkkCf9OPCSbYlI9O0AsEuk4Iwf/NyIH/0Yy - Gf/Tzsj/////////////////in1u/zEcAP93aVb//Pv7///////////////////////h3tn/UD4m/z0p - D/8+KhD/OSQJ/z0pD/+ckYT/7Oro///////Tzsj/RjIZ/zciB/84Iwf/QCwS6WJSPTuooJQFRTEYoDgj - CP84Iwj/NiEG/6KYi/////////////////+KfW7/MRwA/3dpVv/8+/v///////////////////////f2 - 9f/Szsj/zcjC/87Jw/+wqJ3/Tzwk/zUfBP+MgHD//////6KYi/82IQX/OCMI/zgjCP9FMRigqJ+TBQAA - AABOOyOLOSQJ/zgjCP84Iwj/sKid/////////////////4p9bv8xHAD/d2lW//z7+/////////////// - //////////////////////////////////+Lf2//MhwA/3NkUf/+/v7/sKid/zgjCP84Iwj/OSQJ/048 - I4sAAAAAycO7BUk1Ha04Iwj/OCIH/0ArEf/MxsD/////////////////in1u/zEcAP93aVb//Pv7//// - /////////////////////////////////////////////5iMf/8yHQH/dGVS//79/f/Mx8D/QCsR/zgi - B/84Iwj/STYdrcjEvAV8b10UQzAWzDgjCP84Iwf/PikP/62kmf/9/Pz///////////+KfW7/MRwA/3dp - Vv/8+/v/////////////////////////////////////////////////mIx+/zIdAf90ZVL/+fj3/66m - m/8+KQ//OCMH/zgjCP9EMBbMfW9eFGVVQCZBLBPhNyIH/zgjCP83Igf/Qy8V/5yShf/18/L//////4p9 - bv8xHAD/d2lW//z7+/////////////z8/P/49/b/+fj4//7+/v////////////////+YjH//Mh0B/3Rl - Uv+nnZH/Qy8W/zciB/84Iwj/NyIH/0EsE+FlVUAmhHZmDE48JIE8Jw3zOCMI/zgjCP81IAX/VkQt/9bS - zf/+/v7/in1t/zEcAP93aVb//Pv7////////////x8G6/2hZRP99b13/7uzq/////////////////5aL - fP8yHQH/cWJO/3RlUv80HwP/OCMI/zgjCP88Jw3zTTsjgYF0YwxjUj0AfG5dBE88JV4/KxHkOCMI/zUg - BP91Z1T/kIV2/3NlUv9XRS//Mx4C/3dpVv/8+/v///////////+3sKb/NSAE/0k2Hv/DvLT/1tHM/9XR - y//Mx8D/Z1dD/zMdAv92aFX/cmNP/zUgBP84Iwj/PyoQ5E48JF58blwEY1I9AAAAAAByZVEA////AF5N - OEk+KhDmNR8E/3ZnVP9tXUn/LxkA/zUfBP80HgP/d2hW//z7+////////////9LNx/9CLhX/OCMI/0Iu - FP9DLxb/Qy8W/0AsEv83Igf/NyEG/5CEdf9eTTf/NiAF/z4qEOZdTTdJ////AHBhTgAAAAAAAAAAAAAA - AACBc2MAvreuA0UxGKM1IAX/cWJP/5qQgv9pWUX/Wkgy/0g1HP9/cWD//Pv7////////////+fj4/5SJ - ev9GMhn/OyYM/zwnDf89KA7/PSkO/0MvFv90ZlP/lYp8/z4qD/84Iwj/RTEYo764sAOBdGQAAAAAAAAA - AAAAAAAAAAAAAAAAAAA6JQoASzggXDkkCfxDLxb/bF1J/4ByYf+NgXL/lop8/62kmf/g3dn///////j3 - 9v/f29f/2NTP/8/KxP/CvLT/pZyQ/5WKfP+Wi33/mo+C/4l9bf9HNBv/NyIG/zolCvxLOCBcOiUKAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBdTDYkQCwS4jciB/81HwT/NR8E/zciB/88Jw3/QS0T/1pI - Mv+UiHr/cGBN/0s4H/9KNx//cGBN/5aLff9fTzn/RzMa/0c0G/9BLRT/NyIH/zciBv84Iwj/QCwS4l1M - NiRdTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi39wAI2AcgVWRC1lSDUcqEMvFs0/KxHqOiUK/Dgj - CP84Igf/NyEG/zUgBf81IAT/NyIH/zciB/81IAX/NSAF/zYhBv83Igf/NyIH/zolCvw+KhDqQS0UzUYy - GahUQiplkIR1BYyAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJuRhACzq6IDeGpYEWNT - PipCLhV5OiUK8jgjCP84Iwj/OCMI/zgjCP84Igf/OCIH/zgjCP84Iwj/OCMI/zgjCP85JAryQi4UeWFQ - OypvX0wRpJqOA4x/cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAASzgfAFVDKwRCLhRtOiYL8jgjCP84Iwj/OiUL+0EsE+dBLRPnOyYL+zgjCP84Iwj/OiUL8kEt - E21RPicESTUdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAU0EqAGBPOgRFMhl9QS0T7Ek2HblNOiJpZFQ/MGhYQzBRPidpSzkguUIu - FOxEMRd9XEw2BFE/KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+B///wAA//4A - AH/8AAA//AAAP/AAAA/AAAADwAAAA8AAAAPAAAADwAAAA4AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACAAAABAAAAAAAAAAAAAAAAAAAAAIAAAAHgAAAH4AAAB/AAAA/wAAAP8AAAD/wAAD//gAH//8AD/ygA - AAAwAAAAYAAAAAEAIAAAAAAAACQAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGxc - SACShXcEaVlFT0o3HstINBzLZFM+T4R3ZwRkUz4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACXjH4AAAAAAGdXQyNRPiaTQCsR6zkkCf85JAn/PyoQ6048JJR+cF8v////AIl8bAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1c/OAGtcSAB0ZVMib2BMU4F0 - Y1OMgHBJSTYdIT4pDx1rW0cmXUw2dEIuFNY6JQr+OCMH/zgjCP84Iwj/OCMI/zolC/9HMxrgW0kzdWtb - RyY+KQ8dSTYdIYyAcEmBdGNTb2BMU3RlUyJrXEgAysjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfW9eAIR3 - Zw1MOSGsQS0T/0UxGP5FMRj4OSQK4zolC+FBLRPmPSkO/DgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP83Igf/PSgO/EEtE+Y6JQvhOSQK40UxGPhFMRj+QS0T/0s5IKyEd2YNfW9dAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AACgl4sAAAAAAGFQOmw9KA76NyIH/zciB/83Igf/OCMI/zgjCP84Iwf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjB/84Iwj/OCMI/zciB/83Igf/NyIH/z0oDvpgTzpsAAAAAKKY - jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABXRS4Aa1tHJEQxF9U4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zYhBf82IQX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9EMBfValpGJFZELQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCWiQDEvbUHVEIrmjolCv84Iwj/NiEF/zUgBP81IAT/NSAE/zUg - BP83Igf/OCMI/zgjCP82IQb/OSQJ/2BPOf9gUDr/OSQJ/zUgBP81IAT/NSAE/zUgBP81IAT/NSAE/zUg - BP81IAT/NiEG/zgjCP86JQr/VEEqmsG6sweek4cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl41+AODd2ARtXUleQi4U9DciB/85JAn/YE86/31v - Xf98b13/fG9d/3lrWf9KNx//NB8D/zYgBf9PPSX/n5WI/+zq5//t6+j/oZeK/21dSv94aVf/fG9d/3xv - Xf98b13/fG9d/3ttXP9wYU7/VUMs/zomC/82IQb/Qi4U9GxdSV7h3dkEl41+AAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AGtaRgCNgHAFY1I9OlNBKp9EMBbvOCMI/zgj - CP8+Kg//wbuy/7Wto/+il4v/oZeL/8nEvf+so5j/alpG/4l9bf/b19L//v79////////////7Orn/763 - rv+onpP/opiM/6OYjP+jmIz/opiM/6Sajv+wp53/w721/6ifk/9OOyP/NiEG/0QwFu9TQiqfY1M+Oo2A - cQVrW0cA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wANAAAAqKCUDlxKND9JNh2fQCwS7Tkk - Cf83Igf/OCMI/zgjCP8/KxH/wLqx/11MNv81IAT/NR8E/4+DdP/+/v7/+Pf2//v7+/////////////// - ///Qy8T/Y1I9/z0pD/84Iwj/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TToh/5qPgv+2rqT/RzQb/zYh - Bv85JAn/QCwS7Uc0G59ZRzE/p52RDh4IAAD08e8AAAAAAAAAAAAAAAAAAAAAAJ+WiQCjmo0YX085sj8r - EfM4Iwj/OCMH/zgjCP84Iwj/OCMI/zgjCP8/KxH/wLqx/15NN/82IAX/NSAF/4+DdP////////////// - /////////////+zq5/9jUj3/NB8D/zgjCP84Iwj/OCIH/zgjB/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsm - C/+upZr/in5u/zYgBf84Iwj/OCMH/zgjCP8/KxHzXkw3sqGXihiflIcAAAAAAAAAAAAAAAAAAAAAAIF0 - YwB/cmEtSDUc7TchBv84Iwj/OCMI/zgjCP84Iwj/OCMI/zciB/89KQ7/wLmw/15NN/82IAX/NSAF/4+D - dP///////////////////////////8C6sf88Jw3/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9sXEj/tKyi/zwoDv84Iwj/OCMI/zgjCP83IQb/SDUc7YByYS2CdGQAAAAAAAAA - AAAAAAAAAAAAAFZFLgBWRC0tPysR7TgjCP84Iwj/OCMI/zgjCP83Igf/NSAE/zwoDf9rXEj/1NDK/11M - Nv82IAX/NSAF/4+DdP///////////////////////////6KYi/82IQb/NyIG/0s4IP+6s6n/y8W+/8rE - vP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYhBv9RPyj/vLSr/0g0HP83Igf/OCMI/zgjCP84Iwj/PysR7VZE - LS1XRS4AAAAAAAAAAAAAAAAAAAAAAEEtEwBINBsxOSUK7zgjCP84Iwj/OCMI/zgjB/9ALBL/cGFO/7ix - p//y8O7/8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////5aKfP8yHAH/Mx0C/089 - Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9KNx//vreu/1NBKv83IQb/OCMI/zgj - CP84Iwj/OSUK70c0GzFBLRMAAAAAAAAAAAAAAAAAAAAAAGVUPwB+cF5eQCwS/jgiB/84Iwj/OCMI/zYg - Bf+GeWj/9/b1////////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ix - p/93aFb/d2lX/4p+bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9INBv/1tHM/4p9 - bf81IAX/OCMI/zgjCP84Igf/QCwS/n5wXl5lVD8AAAAAAAAAAAAAAAAAAAAAAFpJMwBsXEhpPysR/zgj - CP84Iwj/OCMI/zYhBv+glYj/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////7+/v/+/f3//v79//7+/v////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/3NjT/6GYi/82IQb/OCMI/zgjCP84Iwf/PysR/2xdSWlbSTMAAAAAAAAAAAAAAAAAn5WIAAAA - AABPPSV9OiYL/zgjCP84Iwj/OCMI/zgjCP+tpZr/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/3NjT/6+nnP84Iwj/OCMI/zgjCP84Iwj/OiYL/089JX0AAAAAn5SIAAAA - AAD///8AY1M+AG5fS0BFMRjnNyIH/zgjCP84Iwj/OCMI/zkkCf+8taz/////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+/uLD/PCgN/zciBv9HNBv/3dnU/764r/85JAn/OCMI/zgjCP84Iwj/NyIH/0Ux - GOdsXUlAYlE8AP///wB7bVsAgHJhH0s4IMY4Iwj/OCMI/zgjCP84Iwj/NyIH/0MvFf/OycP///////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////+/ubD/PCgN/zciBv9HNBv/3NjT/9HMxf9DLxX/NyIH/zgj - CP84Iwj/OCMI/zgjCP9LOCDGf3JgH3ttWwCimYwNUkAomjolC/84Iwj/OCMI/zgjCP84Iwj/OCMI/5CE - df/6+vn/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////++t67/PCcN/zciBv9HNBv/2tbQ//38 - /P+RhXb/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQv/UT8omqCWiQ1ZSDJ6PioQ9jgjB/84Iwj/OCMI/zgj - CP82IAX/bl9L/+3r6f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+qoZX/OCMI/zci - B/9HNBv/2dXQ///////u7Or/cWJO/zYgBf84Iwj/OCMI/zgjCP84Iwf/PioP9llHMXpKNx/tNyIH/zgj - CP84Iwj/OCMI/zYhBv9UQiv/19LN////////////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9PO - yP9fTjj/NiEG/zUgBP9PPCT/4t/c////////////2NTP/1RCK/82IQb/OCMI/zgjCP84Iwj/NyIH/0s3 - H+1XRS+1OiUK/zgjCP84Iwj/OCMI/zUgBP9uX0z/9/b1////////////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP///////////////////////////////////////////+Th3v91Z1T/aFhE/2lZ - Rf9pWUX/ZlVB/0o3Hv83Igf/OyYL/0s4IP+flYj//Pv7////////////9/b1/25fTP81IAT/OCMI/zgj - CP84Iwj/OiUK/1dGL7VrW0hMQCsS7zgjB/84Iwj/OCMI/zciB/9FMRj/zsnC//////////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////////////////////9vX - 0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c1//8+/v/////////////////zsnC/0Ux - GP83Igf/OCMI/zgjCP84Iwf/QCsR72tbR0yglooMUj8ouTgjCP84Iwj/OCMI/zgjCP81IAX/iX1t//7+ - /f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - /////////////9vX0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c2P/8+/v///////// - ///+/f3/iX1t/zUgBf84Iwj/OCMI/zgjCP84Iwj/Uj8ouaGXiwwbAwAAZVVAYD0pD/o4Iwj/OCMI/zgj - CP82IQb/UD4m/+Dd2P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - /////////////////////////////+Th3v91Z1T/aFhE/2lZRf9pWUX/ZlVB/0k2Hv83Igf/OyYL/0s4 - IP+glon//Pv7///////g3Nj/UD4m/zYhBv84Iwj/OCMI/zgjCP89KQ/6ZVVAYBsDAABtXUoAcWNPE0Et - FMY4Iwj/OCMI/zgjCP84Iwf/PSkP/8S+tv//////////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9LO - yP9eTjj/NiEG/zUgBP9PPCT/4+Dc///////Evrb/PSkP/zgjCP84Iwj/OCMI/zgjCP9BLRTGcWJQE21d - SgCwp5wAxb63C1I/KL83Igf/OCMI/zgjCP83Igf/RzMa/9nW0P//////////////////////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// - //////////////////+qoZX/OCMI/zciB/9HNBv/2dXQ///////Z1dD/RjMa/zciB/84Iwj/OCMI/zci - B/9SQCi/xsG4C7CpngByYk8AdmdVFUUxGNU4Iwj/OCMI/zgjCP82IQb/WEYw/+vp5v////////////// - ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// - //////////////////////////////////++t6//PCcN/zciBv9HNBv/2dXQ///////r6eb/WEYv/zYh - Bv84Iwj/OCMI/zgjCP9FMhjVdmhVFXFjUAB+cF4Af3FgNkUxGPA3Igf/OCMI/zgjCP81IAT/b2BN//n5 - +P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// - //////////////////////////////////////////////////+/ubD/PCgN/zciBv9HNBv/2dXQ//// - ///5+fj/b2BN/zUgBP84Iwj/OCMI/zciB/9FMRjwf3JgNn5wXwBKNh4AXUw2Sj0oDvk4Iwj/OCMI/zgj - CP82IAX/bF1J/+7s6v//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// - //////////////////////////////////////////////////////////////////+/uLD/PCgN/zci - Bv9HNBv/2dXQ///////v7ev/bV1J/zYgBf84Iwj/OCMI/zgjCP89KA75XUw2Sko3HgA6JgsAY1M+ejwn - Df84Iwj/OCMI/zgjCP84Iwj/OiUL/3lqWP/j4N3/////////////////8fDu/1tKM/82IAX/NSAF/4+D - dP////////////////////////////////////////////////////////////////////////////// - //+/uLD/PCgN/zciBv9HNBv/2tbR/+bk4P96bFr/OyYL/zgjCP84Iwj/OCMI/zgjCP88Jw3/ZFM+ejsm - CwAAAAAATz0ljDkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zYhBv9hUDr/zsnC//7+/v//////8fDu/1tK - M/82IAX/NSAF/4+DdP////////////////////////////7+/v/+/f3//v79//7+/v////////////// - //////////////////+/uLD/PCgN/zciBv9JNR3/x8G5/21dSf82IAX/OCMI/zgjCP84Iwj/OCMI/zgj - CP85JAn/Tz0ljAAAAAAAAAAAcGFOREo2HtQ4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP83Igf/emxa//Dv - 7f//////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ixp/93aFb/d2lX/4p+ - bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9JNh3/urKo/047I/83IQb/OCMI/zgj - CP84Iwj/OCMI/zgjCP9JNh3Ubl9LRAAAAADU0MsAAAAAAGFRPCROOyO3OiYL/TgjB/84Iwj/OCMI/zci - B/9KNh7/x8G5/8zGv//X087/2tbR/1hHMP82IQX/NSAF/4+DdP///////////////////////////5SJ - e/8yHAH/Mx0C/089Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9LNx//u7Oq/088 - JP83Igb/OCMI/zgjCP84Iwf/OiYL/U47I7dhUTwkAAAAANTQywAAAAAAxL61AEUxGQCAcmEUSzggiz8r - Efo4Iwj/OCMI/zciBv9PPSX/u7Oq/048JP9EMRf/UD4m/z4qEP84Iwj/NSAF/4+DdP////////////// - /////////////52Thv82IQb/NyIG/0s4IP+6s6n/y8W+/8rEvP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYh - Bv9RPyj/vLWs/0g1HP83Igf/OCMI/zgjCP8/KxH6Sjcfi31vXRRDLhUAxL61AAAAAAAAAAAAAAAAAAAA - AACFeGgAnJOFCmRUP3w+KhDzOCMI/zciBv9PPCX/urKp/0k2Hf82IQb/NiEG/zgjCP84Iwj/NSAF/4+D - dP///////////////////////////7qyqf86JQr/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm - DP83Igb/OCMI/zUgBP9rXEj/tK2i/zwoDv84Iwj/OCMI/z4qEPNjUz58mY+BCoJ2ZQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAjIBwAJyShAhJNh61OCMI/zciBv9PPSX/urKp/0c0G/80HgP/NSAF/zYh - Bv83Igf/NSAE/4+DdP///////////////////////////+Xi3/9ZSDH/NB8E/zgjCP84Iwj/OCIH/zgj - B/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsmC/+tpZr/in5u/zYgBf84Iwj/OCMI/0o3HrWckoUIjIBwAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJGEACgRAABUQituOyYL/zciB/9KNh7/x8G5/6GX - iv+BdGP/bV5K/1hHMP9KNx7/OycM/5CEdf/////////////////////////////////Fv7f/XUw2/zwo - Df83Igf/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TDoh/5qPgf+2rqT/RzQb/zciB/84Iwj/OyYL/1RC - K24oEQAAnJKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBiUjwtQCsR6Dci - B/86JQr/ZVVA/4p+bv+imIz/same/722rf+/uK//uLGn/9XRy//5+Pj///////////////////////z8 - +//9/fz/7evo/8bAuP+so5j/p52S/6Sajv+imIz/opiM/6Sajv+wp5z/w721/6ifk/9OOyP/NiEG/zgj - CP84Iwj/QCsR6GJSPC1dTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHhq - VwCNgnIIRTIYszgjCP84Iwj/NiAF/zUgBP82IQb/OyYM/0MwFv9QPSb/ZFQ//3FiT/9+cF//4d3Z//f2 - 9f/c2NP/saie/31vXf98b13/rqWa/9vY0//5+Pf/4+Hd/4x/cP97blz/fG9d/3ttXP9wYU7/VUMs/zom - C/83Igb/OCMI/zgjCP84Iwj/RTIZs46Dcwh4a1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAMG5sAAvGQAAXEo0ejsmDP83Igf/NyIH/zgjCP84Iwj/OCMI/zciB/83IQb/NSAF/zUg - BP82IQX/YE85/21eSv9JNh3/OSQJ/zUgBP81IAT/OCMI/0g0HP9rW0f/YE86/zYgBf81HwT/NSAE/zUg - BP81IAT/NiEG/zgjCP84Iwj/NyIH/zciB/87Jgz/XEs1ei4YAADEvrQAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYRjAAbl5LLFNBKrZEMRfaQi4U9DwnDfw5JQr/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/NiEF/zUgBf83Igf/OCMI/zgjCP84Iwj/OCMI/zciB/81IAX/NiEF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf87Jgz8QCwS9EIuFdpRPie2bl9MLFhHMQAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARTEYALGpngp6bFoaa1xIO2JS - PWRRPyiMTjsjvzolC/A4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQrwTjsjv088JIxdSzVkZFM+O3FiTxqil4sKNCAGAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAPb19AD///8BnpOGCEIuFWY7JgzwOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsmC/BBLRRmnZOGCP///wH29fQAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkMrAG5dSQJGMxlmPCcN7zgjCP84Iwj/OCMI/zgj - CP84Iwf/OCMI/zsmC/87Jgv/OCMI/zciB/84Iwj/OCMI/zgjCP84Iwj/PCcM70QwF2ZmVkMCUT8oAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFlHMABxYU4CSTYdZT0o - Du84Igf/OCMJ/zwnDP1AKxHmSTYdvV1MNoleTTiJTDkhvUEtE+Y8KA39OCQJ/zciB/88KA3vSDUcZWlb - RgJUQywAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AABjUj0Af3FiA007InRFMhjvTjsjw11NN3xlVUAyi39vC////wH///8BlIl7C21dSTJjUz58UT8ow0cz - Gu9MOCB0eGpWA19ONwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///gf//8AAP//8A///wAA//AAAA//AAD/4AAAB/8AAP/gAAAH/wAA/8AAAAP/ - AAD/gAAAAf8AAP8AAAAA/wAA/AAAAAA/AADwAAAAAA8AAOAAAAAABwAA4AAAAAAHAADgAAAAAAcAAOAA - AAAABwAA4AAAAAAHAADgAAAAAAcAAOAAAAAABwAAwAAAAAADAACAAAAAAAEAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA - AAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAMAAAAAAAwAA4AAAAAAH - AADwAAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAA/gAAAAB/AAD+AAAAAH8AAP8A - AAAA/wAA//AAAA//AAD//AAAP/8AAP/+AAB//wAA//8AAP//AAAoAAAAoAAAAEABAAABACAAAAAAAACQ - AQATCwAAEwsAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AHNjUf9zY1H/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Am5CD/z4pD/84Iwj/OCMI/zsnDP+NgXL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/UD0m/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0w4IP+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBvXkv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2dX - RP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI2Bcv87Jgv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/4FzYv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Atq2j/0k1Hf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/6eek/////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AY1Q//zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/X085/9DMxv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCNgXL/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/gXNi/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/9DLxf/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9AKxL/m5CD//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ApJuQ/3ttW/+Ie2r/kIN0/5eMfv+il4z/q6OW/7Ssov+9tq3/yMK6/9DMxv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AysS+/1hHMP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9YRzD/ycS9/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDQzMb/yMK6/722rf+0rKL/q6OW/6KXjP+XjH7/kIN0/4h7 - av97bVv/pJuQ/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9CLhT/Sjcf/1NA - KP9fTzn/Xk02/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/X085/1NAKP9KNx//Qi4U/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zolCv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGJRPf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKmg - lP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBDLxf/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wB6alj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP96bFr/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDHwrn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/8O9tf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AU0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5eMfv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKq - oP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/sqqg//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBINRz/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wCDd2X/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OSQJ/1dGLv/Iwrr/yMK6/1dGLv84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/gnRk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS/5uQg/////////////// - ////////p56T/0IuFP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/MxsD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AWEcw/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/emxa////////////////////////////////////////////iHtq/zkk - Cf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8An5aI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZW - Qv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/dGRT/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/0MzG//// - ///////////////////////////////////////////////////QzMb/Xk02/zgjCP84Iwj/Qi4U/1xL - Nf9pWUX/dGRT/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3ps - Wv96bFr/emxa/3psWv9uYEz/ZlVB/048JP86JQr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Av7ev/z8qEP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - ////////////////////////////////////////////////////////RjMZ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf+tpJj///////////////////////////////////////// - ///////////////////////////////////KxL7///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////8/Lxf+hl4n/Y1M+/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PyoQ/7+3r/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wC0rKL/V0Yu/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////////////////////////////////////////////////// - /////////////////////////////3xuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/4l+ - bv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////iX5u/zsnDP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/V0Yu/7Ss - ov////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9OPCT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP/JxL3///////// - //+Ie2r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZlZC//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////8O9 - tf+2raP/qJ2S/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KX - jP+il4z/opeM/6Sajv+yqZ//wLmx/87Iwv//////////////////////////////////////altG/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk/6eek/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8Am5CD/0c0G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy///////////////////////Szsj/ycS9/722 - rf+2raP/wryz//////////////////////////////////////////////////////////////////// - /////////////////////////////7Cnnf92Z1X/RjMZ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/1VDLP+Qg3T///////////////////////////+ajoH/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RzQb/52RhP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCQg3T/QS0T/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////pp2R/0Yz - Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r//////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/5CEdf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AI2Bcv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsnDP+kmo7///////// - ////////lIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/PCgN/4FzYv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AgXNi/zol - Cv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////VUMs/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////////9mVUH/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/3Nl - Uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMnEvf9sXEj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ZlZC/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/21dSf/KxL7/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Awry0/2NT - Pv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////pp2R/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv////////////////9/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1xLNf+2raP/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AubOq/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////0c0 - G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/dGZU/////////////////zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7mxqP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AKmglP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9eTTb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////9lVUD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/k4d3////////////m5CD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AjIBx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////PCgN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1VELf///////////9HM - x/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AHtuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OSQJ/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9/cmD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBvXkv/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////6adkf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/PCgN/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/YlI8/0MvF/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8S+t////////////1dGLv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/b15L/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AX085/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+PgnT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - ////////////////////////////////////////////////////////////////////////wbux/1E+ - J/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+onZL///////// - //9wYU3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/19POf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFA+Jv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPSb/q6OW//////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////gnRk/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9QPib/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBCLhT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1pIMf+2raP///////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////3ZnVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8jCuv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv///////////5CEdf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/7ato////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWkX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////UT4n/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Bc2L///////// - //+YjYD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9wYU3/zsjC//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OiUK/3xuXf////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2hXQ/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////7KqoP86JQr/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDPycL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9oV0P/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/PycL/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AwLmx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/1tKNf////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////9bSTT/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/wLmx/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKpn/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////aVpF/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7KqoP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCimY3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/empY//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AlIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4d7a/////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+GeWj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/lIp7/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIV3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+XjH7///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////lot8/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4Z5aP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB1aFT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pp2R//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////6SbkP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AZ1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Ssov////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+yqqD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEYzGf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/CvLT///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////wry0/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AG9eS/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0czH//////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/altG/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJuQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+bkIP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AMzGwP86JQr/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/8jC - uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBMOCD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9JNR3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBsXEj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/RTEY//////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////QzAW/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/2lZRf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCXjH7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1RC - Kv////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////1NAKP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/mI2A/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wDGwLj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+elIb///////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //////////////////+elIb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf/GwLj/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ARzQb/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zkkCf9sXEj///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////25g - TP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZlZC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9KNx////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////TToi/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9iUT3/////AP///wD///8A////AP///wD///8A////AP///wD///8Ak4d3/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jwz/zsjC//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3dl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////9POyP88KA3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5KH - eP////8A////AP///wD///8A////AP///wD///8Aw762/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/opmN//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////opeM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wLmx/////wD///8A////AP// - /wD///8A////AEYzGf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cWJP//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9iUjz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////92Z1X/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0UxGP////8A////AP///wD///8A////AGNUP/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////1E+J/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/YVA7/////wD///8A////AI2Bcv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OycM/9DM - xv////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////////////////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+PgnT/////AMK8 - tP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////1pIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3tuXP///////////////////////////////////////////////////////////6ad - kf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7+3r/9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////4l8 - bP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+QhHX///////// - ////////////////////////////////////////////////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9TQCj/lYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1A9Jv////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3tuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wryz//////////////////////////////////// - //////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/l4x+//// - /wA7Jwz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //+5s6r/hnlo/0ArEv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/UT4n//////////////////////////////////////////////////////////////////// - /////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/////wD///8Aemxa/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/w721//////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/8nEvf////////////// - /////////////////////////////////////////////////////////////8K8tP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3ttW/////8A////AM7Iwv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29eS/////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QCsS/7u1rP////////////////////////////////////////////// - //////////////////////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Py8X/////AP// - /wD///8AYU86/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9HNBv/hnlo//// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/////AP///wD///8A////ALSsov84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4p+cP////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/XEs1/722rf////////////////////////////////////////////// - ////////////////////////////////////////////////////////iX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/tKyi/////wD///8A////AP///wD///8ASTYe/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - /////////////////////////////0EtE/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////wD///8A////AP// - /wD///8A////AJiNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3///////////////////////// - /////////////////////////////////////////////////////////////////////////////6Sb - kP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5qOgf////8A////AP///wD///8A////AP///wD///8AOycM/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9QPib///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/fG5d//////////////////////////////////////////////////// - //////////////////////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwo - Df////8A////AP///wD///8A////AP///wD///8A////AHxuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/8G7 - sf////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu - Xf////////////////////////////////////////////////////////////////////////////// - ///////////////////AubH/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3/////AP///wD///8A////AP// - /wD///8A////AP///wDSzsj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9sXEj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/vbat//////////////////// - ////////////////////////////////////////////////////////////////////////aVpF/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/087I/////wD///8A////AP///wD///8A////AP///wD///8A////AGJS - PP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/4d7a/////////////////////////////// - /////////////////////////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wC2r6T/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP+He2v///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/QS0T/722rf////////////////////////////////////////////// - /////////////4Z6af84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7avpP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEo3H/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////w721/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP87Jwz/ysS+//////////////////////////////////////////////////////9AKxL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0k2Hv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCbkIP/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubGo/4J0ZP8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1NAKP////////////// - //////////////////////////////////+il4z/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+bkIP/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/bmBM//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////3RmVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wry0//////////////////////////////////// - ////////bmBM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP88KA3/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wB/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/5GGdv///////////////////////////////////////////4yAcf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/f3Fh/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+vp5z///////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP97blz///////// - //////////////////////////////////+tpJj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/0MzG//////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////ubOq/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - ////////zsjC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDMxsD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/zcjB/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Aq6GV/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0w4IP////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////Y1Q//zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////Sjcf/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6ujlv////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIl8bP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9tX0r///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Jfm7/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBoV0P/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/joRz//////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf////////////////////////////////////////////////+NgXL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8ARzQb/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Cnnf////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////////////////////////////////////r6ec/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP/RzMf///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AMrEvv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////OSQJ/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/8rGv/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/TToi//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////0w4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+poJT/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiHtq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29e - S/////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - //////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/h3tr/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGZVQf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Qg3T///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////joRz/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBGMxn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////////////////////6idkv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p - Dv+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// - /////////////722rf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AycS9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5SKe/////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////////////////////////////////////6KXjP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP/KxL7/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee - k/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cGFN//////////////////////////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - /////////////////////////////3VoVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qaCU//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCGemn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9UQir///////////////////////////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////1hH - MP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4h7av////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0Iu - FP/CvLP///////////////////////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf//////////////////////x8C4/0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9mVUH/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEUx - GP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/6KXjP////////////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - ////////p56T/zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QzAW//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fnFf//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////q6OW/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP// - /wD///8A////AP///wDIwrr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9fTjf///////////////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/yMK6/////wD///8A////AP///wD///8A////AP///wD///8A////AHxu - Xf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2 - Hv/Py8X//////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fG5d//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Ap56T/zolCv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////Z1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wDEvrf/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9RPif/hnlo/3xuXf////////////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2VVQP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9DLxf/xL63/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBVRC3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9QPSb///////// - /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9nV0T/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// - //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9VRC3/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AHNj - Uf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////ZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9zY1H/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Alot8/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////8G7sf+7s6v///////////////////////////////////////// - ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////2dXRP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////9QPSb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4FzYv///////////5iNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Wi3z/////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wC7s6v/PyoQ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/RjMZ/2JSPP98bl3/mI2A/7Ssov/OyML//////zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////9pWUX/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///JxL3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //+QhHX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/z4pD/+2raP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBPPCX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////dGZU/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/088 - Jf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AGpb - Rv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////4J0ZP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////CvLT/U0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/6idkv///////////3BhTf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZVQf////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiX5u/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////+OhHP/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwoDf9tXUn/bV1J/21dSf9tXUn/bV1J/21d - Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/2JRPf9FMRj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Dvrb///////// - //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4l+bv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wCvp5z/PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OyYL/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/6mglP////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AM/Lxf9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VUMs////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/STYe/8/Lxf////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFVE - Lf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - ////////////////////////////////////////////////////////////////////////Qi4U/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5GGdv///////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - /////////////////////////////////////////////39yYP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9fTzn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6mglP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////OyML/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9zZVL/////////////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wBiUT3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// - //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////3Bh - Tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP89KQ7/////////////////gXNi/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9jVD//////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ApJqO/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////Qy8X/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/pp2R/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pJuQ/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////8rEvv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//// - /////////////2ZWQv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AX043/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Sh3j///////// - ////////wry0/6adkf+JfGz/bV1J/1A9Jv85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////z8nC/0k2Hv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/opeM/////////////////5aLfP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/19ON/////8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJ+WiP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZVVA//////////////////////////////////// - ///////////////////AubH/pJqO/4Z6af9qW0b/TToi/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgj - CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////hnlo/zsm - C/84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP9qW0b//////////////////////6ujlv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+PgnT///////////////////////////////////////////////////////// - /////////////////////////////722rf+gl4v/g3hm/2hXQ/9JNh7/OCMI/zgjCP+NgXL///////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////npSG/2lZRf8/KhD/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUMs/4+CdP///////////////////////////5uQ - g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP8+KQ//W0k0/3doVf+Uinv/sKme/87Iwv////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////AubH/sqqg/6adkf+il4z/opeM/6KX - jP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+imY3/sKme/7+3 - r//OyML//////////////////////////////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Wkgx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/0EtE/9eTTb/emxa/5eMfv+0rKL/0czH//////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////jIBx/zwoDf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5mQgv////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2FPOv98bl3/mZCC/7ewpv////////////////////////////// - //+2r6T///////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////0MzG/6KZjf9lVUD/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBUQir/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/Y1M+/3VoVP9INRz/OCMI/6ujlv////////////// - /////////////////////////////////////////////////////////////62kmP9xYk//PyoQ/z8q - EP9xYk//raSY//////////////////////////////////////////////////////////////////// - ////////sKme/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/bmBM/2ZV - Qf9OPCT/OyYL/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9UQir/////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AlIh5/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////// - //////////////////+il4z/ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/WEcw/5SK - e//Py8X/////////////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/lot8/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA5JAn/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+elIb////////////////////////////CvLT/iHtq/006Iv84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0c0G/96bFr/tq2j//// - ////////////////////////pJqO/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8ATzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/52R - hP//////r6ec/3NlUv89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/ZlVB/6KXjP//////opmN/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/048JP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI+C - dP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP+Qg3T/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0MzG//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wCKfnD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNR3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2ZVQf+NgXL/tq2j/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9DLxf/ZlVB/42B - cv+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl - Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj - CP84Iwj/OCMI/0MwFv9mVUH/jYFy/7ato/////8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDAubH/mI2A/3Bh - Tf9CLhT/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/3BhTf+YjYD/wLmx/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/STYe/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8ATToi/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wBRPif/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/Tjwk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFNA - KP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tzwl//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AVEIq/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/UD0m/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wBYRzD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/VEIq/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AW0k0/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/V0Yu/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBcTDb/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/QCsS/3ZnVf+wqZ7/sKme/3ZnVf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEw2//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AGFQO/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2Hv+Bc2L/vbat/////wD///8A////AP// - /wD///8A////AMnEvf+NgXL/Tzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEs1/////wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AYlE9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zkk - Cf9fTzn/m5CD/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8Ap56T/21dSf87Jgv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj - CP84Iwj/X043/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBjVD//OCMI/zgj - CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD/c2VS/6+nnP////8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AL22 - rf+Bc2L/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/UD4m/4h7 - av/CvLT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDPy8X/lIp7/1dG - Lv84Iwj/OCMI/zgjCP84Iwj/ZlVB/////wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8AbV9K/19POf+bkIP/////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9tXUn/bFtH//// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - //////////5/////////////////////////+B/////////////////////////gB/////////////// - /////////8AD////////////////////////AAD///////////////////////wAAD////////////// - ////////+AAAD//////////////////////gAAAH/////////////////////4AAAAH///////////// - ////wAf+AAAAAH/gA//////////////AAAAAAAAAAAAD/////////////4AAAAAAAAAAAAH///////// - ////AAAAAAAAAAAAAP////////////8AAAAAAAAAAAAA/////////////gAAAAAAAAAAAAB///////// - ///8AAAAAAAAAAAAAD////////////wAAAAAAAAAAAAAP///////////+AAAAAAAAAAAAAAf//////// - ///4AAAAAAAAAAAAAB////////////AAAAAAAAAAAAAAD///////////4AAAAAAAAAAAAAAH//////// - ///gAAAAAAAAAAAAAAf//////////8AAAAAAAAAAAAAAA///////////gAAAAAAAAAAAAAAB//////// - //+AAAAAAAAAAAAAAAH//////////wAAAAAAAAAAAAAAAP/////////+AAAAAAAAAAAAAAAAf/////// - //gAAAAAAAAAAAAAAAAf////////4AAAAAAAAAAAAAAAAAf///////+AAAAAAAAAAAAAAAAAAf////// - /gAAAAAAAAAAAAAAAAAAf//////4AAAAAAAAAAAAAAAAAAAf/////+AAAAAAAAAAAAAAAAAAAAf///// - AAAAAAAAAAAAAAAAAAAAAP////wAAAAAAAAAAAAAAAAAAAAAP///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 - AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w - AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///4AAAAAAAAAAAAAAAAAAAAAAH///A - AAAAAAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAB//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAD+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAA4AA - AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAYAA - AAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAA+AA - AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAD/AA - AAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH//AAAAAAAAAAAAAAAAAAAAAAAA//8A - AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A - AAAAAAAAAAAAAAAAAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA - AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA - AAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/wA - AAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAAAAAAAB///A - AAAAAAAAAAAAAAAAAAAAAAP//+AAAAAAAAAAAAAAAAAAAAAAB///8AAAAAAAAAAAAAAAAAAAAAAP///8 - AAAAAAAAAAAAAAAAAAAAAD////4AAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAAAAAAAAAAAAAD///// - gAAAAAAAAAAAAAAAAAAAAf/////AAAAAAAAAAAAAAAAAAAAD//////AAAAAAAAAAAAAAAAAAAA////// - 8AAAAAAAAAAAAAAAAAAAD//////4AAAAAAAAAAAAAAAAAAAf//////gAAAAAAAAAAAAAAAAAAB////// - +AAAAAAAAAAAAAAAAAAAH//////8AAAAAAAAAAAAAAAAAAA///////wAAAAAAAAAAAAAAAAAAD////// - /AAAAAAAAAAAAAAAAAAAP//////+AAAAAAAAAAAAAAAAAAB///////4AAAAAAAAAAAAAAAAAAH////// - /gAAAAAAAAAAAAAAAAAAf///////AAAAAAAAAAAAAAAAAAD///////8AAAAAAAAAAAAAAAAAAP////// - /wAAAAAAAAAAAAAAAAAA////////gAAAAAAAAAAAAAAAAAH///////+AAAAAAAAAAAAAAAAAAf////// - /4AAAAAAAAAAAAAAAAAB////////gAAAAAAAAAAAAAAAAAH////////AAAAAAAAAAAAAAAAAA/////// - /8AAAAAAAAAAAAAAAAAD////////8AAAAAAAAAAAAAAAAA//////////gAAAAAAAAAAAAAAB//////// - ///8AAAAAAAAAAAAAD/////////////gAAAAAAAAAAAH//////////////4AAAAAAAAAAH////////// - /////wAAAAAAAAAA////////////////gAAAAAAAAAH////////////////AAAAAAAAAA/////////// - /////+AAAAAAAAAH////////////////8AAAAAAAAA/////////////////4AAAAAAAAH/////////// - //////wAAAAAAAA//////////////////gAAAAAAAH//////////////////AAAH4AAA//////////// - //////+AAH/+AAH//////////////////8AD///AA///////////////////4B////gH//////////// - ///////x/////4////////// - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Installer.UI/Program.cs b/legacy/V3/TGS.Installer.UI/Program.cs deleted file mode 100644 index 851a948e3b..0000000000 --- a/legacy/V3/TGS.Installer.UI/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Windows.Forms; - -namespace TGS.Installer.UI -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Main()); - } - } -} diff --git a/legacy/V3/TGS.Installer.UI/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Installer.UI/Properties/AssemblyInfo.cs deleted file mode 100644 index debafbb073..0000000000 --- a/legacy/V3/TGS.Installer.UI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TGStation Server Installer")] -[assembly: AssemblyDescription("Installation manager for /tg/station Server")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8956d4c3-bfb9-448e-bf5f-ee7e6f9996f9")] diff --git a/legacy/V3/TGS.Installer.UI/Properties/Resources.Designer.cs b/legacy/V3/TGS.Installer.UI/Properties/Resources.Designer.cs deleted file mode 100644 index 9a0670c601..0000000000 --- a/legacy/V3/TGS.Installer.UI/Properties/Resources.Designer.cs +++ /dev/null @@ -1,83 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace TGS.Installer.UI.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TGS.Installer.UI.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized resource of type System.Byte[]. - /// - internal static byte[] cab1 { - get { - object obj = ResourceManager.GetObject("cab1", resourceCulture); - return ((byte[])(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Byte[]. - /// - internal static byte[] TGSInstaller { - get { - object obj = ResourceManager.GetObject("TGSInstaller", resourceCulture); - return ((byte[])(obj)); - } - } - } -} diff --git a/legacy/V3/TGS.Installer.UI/Properties/Resources.resx b/legacy/V3/TGS.Installer.UI/Properties/Resources.resx deleted file mode 100644 index 7e63714e76..0000000000 --- a/legacy/V3/TGS.Installer.UI/Properties/Resources.resx +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\..\TGS.Installer\bin\Release\cab1.cab;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ..\..\TGS.Installer\bin\Release\TGServiceInstaller.msi;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/legacy/V3/TGS.Installer.UI/TGS.Installer.UI.csproj b/legacy/V3/TGS.Installer.UI/TGS.Installer.UI.csproj deleted file mode 100644 index 0db0a2fa49..0000000000 --- a/legacy/V3/TGS.Installer.UI/TGS.Installer.UI.csproj +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Debug - AnyCPU - {8956D4C3-BFB9-448E-BF5F-EE7E6F9996F9} - WinExe - TGS.Installer.UI - TG Station Server Installer - v4.5.2 - 512 - true - - - - - app.manifest - - - tgs.ico - - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\Release\ - TRACE - bin\x86\Release\TG Station Server Installer.xml - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - false - - - - ..\packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll - False - - - ..\packages\WiX.3.11.0\tools\Microsoft.Deployment.WindowsInstaller.dll - - - - - - - - - Form - - - Main.cs - - - - - True - True - Resources.resx - - - - Main.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - - - - - - - - {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} - TGS.Interface - - - {f32eda25-0855-411c-af5e-f0d042917e2d} - TGS.Server - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Installer.UI/app.manifest b/legacy/V3/TGS.Installer.UI/app.manifest deleted file mode 100644 index 467015914e..0000000000 --- a/legacy/V3/TGS.Installer.UI/app.manifest +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/legacy/V3/TGS.Installer.UI/packages.config b/legacy/V3/TGS.Installer.UI/packages.config deleted file mode 100644 index c77cd9eca7..0000000000 --- a/legacy/V3/TGS.Installer.UI/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Installer.UI/tgs.ico b/legacy/V3/TGS.Installer.UI/tgs.ico deleted file mode 100644 index 6ed69fadfcdf8deb049f66dfffc46d5361332feb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123198 zcmeHQ2b>f|_HG1(QxxyiJH1mu&xnDbAQ>bqf<%dugM>v;q9P!>AR-bJ0TGd8IK_aX z2#5hz#f;%jFx}~`r~lK_Logl)QvdI(nVPNX?V0K7shQnbrhdPk?y0V>diCn-SFc{Z zs&<@mPDSUKV;r2TJ0mJMP7lX%PB zX4{+n+bBCg5VM}LHT8#F$viu1R(UX^)tZ+V_Gx?kJFQ0BSK&->jo zecN3mM)kc^4DH@rEW2%nSTJ*<7}B+=xUyR_F|boZaasN9&s zh;pEBOrtZae1USrH3Qp=F@rjaZ~pUd@%5MgC$2(XU7MdHx?WO4^lDQ}4DZoGG&=Lh zFD||4PhcZ@>PM_~5;_BpuAV zX^pCa&+yq^IW>AHEX@4(t~{e7{E={PkDy%f5YL+>p-V z=E)-kcp&&p&(<|hYH`8w%fY7uqx!a$&pfx`(a`hXe+Tl4!vB8$NsR2%Myy_P3+mTF z@EJY}IzDB+pIEa}CVuv*N1~qpcGuTp@hy|Z^;ca1`kRQ4Klqzi|Hx{#3G47_t6G1! zsotp-53=qI&&7SdsrcY;Z^=6F89v+Q!aokh_5S?vgFT;@k@%7H_iim7dtjNEdEGF< zXZURE+9%vhoDAsHKz#c555)5u!}a&z-`K&O#KM~=$@=jbKHI**sTVapr|MSLXKvm# zV$QUy#o)`E2-SY$(0=b|?Kh%VOVQ}8D&T+Bt-#Vnt!kg(bZTA6OsOvHqVc1Fuzh z{sHB#0?vkYYJA42mz;O3ELZ;l9-N#yCsqPKX5n4?>XP%1U5oP8qOM;7Z?n)|l@O;t z;uKbLLOVhJ*Zn|!G1+-^WT0Wa7P(}^v+^9;2hNn$Ko-4qU>EQ3(;Ne%~DG%`I zYs4+*J*Mf<@YHDoJ2$M-&n4g7W!*M&qEiig1AYcVZtMk?iD#K7+f4nhTRjx7{xdCP z*FZjd>x}X4Y~}xX)5kfLaW$}0gY$utop?te_ZkS)N1)9`>pCY&oZmWQoQxaLv4MPd z`n9ho+Fe|Y*xEUuWBv0FM;^>eg_U>Ac0T^_y>h+V);SeqD&JjGW9&nkMb^QqW?m=#R0PIhM>^pVG+pm|qYkr^7X0JG9XQ?-YgR6j@LeZ@SP_Z4kwIpARtbg^iE>Mf@GIRE3>?eG_yaE9TrVTJoxHwF;CmKcd|l zUD2ktYzDN?7+~Pni>j3uQ?KqTAN}vw#q(Er>Svih+MoU5v@!h!^I%@>Ep=EXdjH~P37$j0w4Mw{S$5U&($PDp=L{=`4< zdHYS*iq~Iy4tbm^`OiGNwKz8idR`0Q^9$9+khfCSke7bnFZP{$Z~8z-RPvU2GVd3k z+i)0k)SOToRHB|JmMxee=H-u}o*=G*?xf_y%Uhoo4Iyv5K?ikO)I8n+*6N~8pLT9?hL{YUitorpr<9lSLdmyJ|Ng$rgE9^Mi>G`;-}7nD zoSJnZ`_NA{mhu5I*4elBf93EV-i^Lm>Z3CzUMY3AJLXIkKmG8%WI1uO`H3}R`h+23 z9CRXGK2ygdO_yA7+}-WBS5Sfd~#}4j@qmx*% zXqLEf;!yN6^(EffztsP8C1QR*^Kk1DETivbb)6xXH#ru38wI|vY<}+VUPYVB_m1x) z56V{_MgJ?3=AXdtSHZ89$a~b_E{%_cE~UsWcE0D%xz6j`o-c>GABE?}W6bdbvBd*z z%ew7X{~Mfs#1Fv9_~AWU9<}MQzm!`zYf`cNVnqN)4#o}@(Kk&;-}(*ut98IxE_AS5 z=vwR0R^NaR)6t(*#P}yxHjbDsoV`vpdF*kjI{DC++(*oZwHpOad51@#Q%N4o^PrO_ z$9vV0t~ri~9)*FFB{pip4h{KIQ7Zai2B0 zIkI;fnH=N3Gogn!zj{FX+IY`S2A*m{p3Z~*`n76LwB>(+uZ4ZuU09Q8n3m7*S!Oc4 zXA6^}Wm@2$xUG)*y@B@Jhqm4YJ*E(4eFJPARAs9(Fm(|9`!`I(w0wro0=LyMMm8#c z(#W39a~szl2Hej>zJfBCvY+p9eICFxuc=S+;C1v7O3P=V56pXJ{lkY{JAzh(#NneI z?>NvUocnY|6wQP0oq$Uv}!TNtW(5;&|z-8cBs^+IIf0l z6yJZh8)L}3#ge%RFTlmi=qGaX#|=9E>i(BSSkb=WY0&Q*4u;%{!WH%9+$&niI@dnQk-GVT{re-` z?E4sdQ~xJz>m);mSK^xRY8{l9J()JwO&S|A`>#f2w_^nLanqdE zw87H(d6FNhe8{Vrs_e<6FUxHgjAoEyB$KXBlH9K-C}yH^}McrcQl z7fa{m$?~ro(L+4_#DjADp~`1jEOf%K%VqyKv|F>YATQoiyeEFpmqqf6RemIa$Hmfl z(<0@+ymb@D9+9?XSeKc@=lZL99RvKXQFZ4#NBbDZWGW<6K6UUtK89)cABkIZ1sJ;E-cz+I`e} z$TC@W$Hu4Mt$0saP8mbnpdJ#H&oK_i&unA7-+0H4kk`v^pNa9wW;_x3Gf0PZ( zQ`M1Wb!`0Sxh(tjm!7TIw(iMm!g~49KbSXcscnHP2lFPfZ1ppJ{168^U3t8h_mTh9 z88}|gy`r@oi&Fp08#^$neD=GX%b;9=&PF@DhyFEH&d-oW=Dp?Vb>*i|xKb4xb%xGE z8*X2`p9iQ*!Oke4tBLv3$4R}C??vG!A$tt0q5u2^_9u?Zsf+6R zCv|t^eSmqhj93A7DB2uPhiv>8vC4Mf!E4qH{kG)A;@Oi0=O$n~mNs5qvrNvJP#4$r zZJmAl93{^pak!8E?|b0!dg%K#t{Kq&xA!l*wfwxi#N|c^&@RAZr+dqDDlh zXn4kv`yoTVM4NmGT_R#zK>YWJ8x0#^1;}+Rr{I^cH*kDV0lNMT$os#L-J3Dq&BYvO zEzDolLRq<&HK@KBH2$|$?LXYmw|%_|u(A6pQ0%S7*%NWlaqhSRF%U<@PH}qjb_!${H(*ZLS$NZR6(C4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl - - - - - - - - - - - - - - - - - - - - Installed AND NOT UPGRADINGPRODUCTCODE - - - - - - - - - - - - - - - - - - - - - - - - - - INSTALLSHORTCUTSTART = 1 - - - - - - - - - - - INSTALLSHORTCUTDESK = 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/legacy/V3/TGS.Installer/TGS.Installer.wixproj b/legacy/V3/TGS.Installer/TGS.Installer.wixproj deleted file mode 100644 index fb6790d2e1..0000000000 --- a/legacy/V3/TGS.Installer/TGS.Installer.wixproj +++ /dev/null @@ -1,110 +0,0 @@ - - - - - Debug - x86 - 3.10 - 154435f6-0890-42d4-9aec-b743d4fbc1cb - 2.0 - TGServiceInstaller - Package - - - - - bin\$(Configuration)\ - obj\$(Configuration)\ - Debug - - - bin\$(Configuration)\ - obj\$(Configuration)\ - True - True - - - - - - - TGS.ControlPanel - {394e7643-6b8c-416f-ab18-95ac12648cdc} - True - True - Binaries;Content;Satellites - INSTALLFOLDER - - - TGS.CommandLine - {89191f69-b18e-4b59-b72e-e12f9b6811a0} - True - True - Binaries;Content;Satellites - INSTALLFOLDER - - - TGS.Interface.Bridge - {9a01ef03-8eae-45cb-8b87-4a17bd904557} - True - True - Binaries;Content;Satellites - INSTALLFOLDER - - - TGS.Server.Service - {3f81e398-b223-4006-b40c-c2800714ce29} - True - True - Binaries;Content;Satellites - INSTALLFOLDER - - - TGS.Server - {f32eda25-0855-411c-af5e-f0d042917e2d} - True - True - Binaries;Content;Satellites - INSTALLFOLDER - - - TGS.Interface - {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} - True - True - Binaries;Content;Satellites - INSTALLFOLDER - - - - - - - - - - - - - - - powershell -Command "& \"$(SolutionDir)Tools/SignBasics.ps1\"" - - - powershell -Command "& \"$(SolutionDir)Tools/SignMSI.ps1\"" - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Installer/packages.config b/legacy/V3/TGS.Installer/packages.config deleted file mode 100644 index 8ab6137aee..0000000000 --- a/legacy/V3/TGS.Installer/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Interface.Bridge/DreamDaemonBridge.cs b/legacy/V3/TGS.Interface.Bridge/DreamDaemonBridge.cs deleted file mode 100644 index 8465beb682..0000000000 --- a/legacy/V3/TGS.Interface.Bridge/DreamDaemonBridge.cs +++ /dev/null @@ -1,35 +0,0 @@ -using RGiesecke.DllExport; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace TGS.Interface.Bridge -{ - /// - /// Holds the proc that DD calls to access - /// - public static class DreamDaemonBridge - { - /// - /// The proc that DD calls to access - /// - /// The number of arguments passed - /// The arguments passed - /// 0 - [DllExport("DDEntryPoint", CallingConvention = CallingConvention.Cdecl)] - public static int DDEntryPoint(int argc, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 0)]string[] args) - { - try - { - var parsedArgs = new List(); - parsedArgs.AddRange(args); - var instance = parsedArgs[0]; - parsedArgs.RemoveAt(0); - using (var I = new Client()) - I.Server.GetInstance(instance).Interop.InteropMessage(String.Join(" ", parsedArgs)); - } - catch { } - return 0; - } - } -} diff --git a/legacy/V3/TGS.Interface.Bridge/FodyWeavers.xml b/legacy/V3/TGS.Interface.Bridge/FodyWeavers.xml deleted file mode 100644 index 43fc6a6308..0000000000 --- a/legacy/V3/TGS.Interface.Bridge/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Interface.Bridge/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Interface.Bridge/Properties/AssemblyInfo.cs deleted file mode 100644 index 7195e9f298..0000000000 --- a/legacy/V3/TGS.Interface.Bridge/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TGStation Server Service DreamDaemon Bridge")] -[assembly: AssemblyDescription("Used by DreamDaemon to call into the TGStation Server Service")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9a01ef03-8eae-45cb-8b87-4a17bd904557")] diff --git a/legacy/V3/TGS.Interface.Bridge/TGS.Interface.Bridge.csproj b/legacy/V3/TGS.Interface.Bridge/TGS.Interface.Bridge.csproj deleted file mode 100644 index d960e47808..0000000000 --- a/legacy/V3/TGS.Interface.Bridge/TGS.Interface.Bridge.csproj +++ /dev/null @@ -1,78 +0,0 @@ - - - - - Debug - AnyCPU - {9A01EF03-8EAE-45CB-8B87-4A17BD904557} - Library - Properties - TGS.Interface.Bridge - TGDreamDaemonBridge - v4.5.2 - 512 - x86 - - - - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - false - - - - - - - - - ..\packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll - False - - - ..\packages\UnmanagedExports.1.2.7\lib\net\RGiesecke.DllExport.Metadata.dll - False - - - - - - - - - - - {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} - TGS.Interface - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Interface.Bridge/packages.config b/legacy/V3/TGS.Interface.Bridge/packages.config deleted file mode 100644 index 3d97405485..0000000000 --- a/legacy/V3/TGS.Interface.Bridge/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Interface/AuthenticationHeaderApplicator.cs b/legacy/V3/TGS.Interface/AuthenticationHeaderApplicator.cs deleted file mode 100644 index 6031f4eb68..0000000000 --- a/legacy/V3/TGS.Interface/AuthenticationHeaderApplicator.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.ServiceModel; -using System.ServiceModel.Channels; -using System.ServiceModel.Dispatcher; -using System.ServiceModel.Description; - -namespace TGS.Interface -{ - /// - /// Used to attach windows credential headers to SOAP messages - /// - sealed class AuthenticationHeaderApplicator : IEndpointBehavior, IClientMessageInspector - { - /// - /// The credentials to attach - /// - readonly RemoteLoginInfo remoteLoginInfo; - - /// - /// Construct a - /// - /// The to use - public AuthenticationHeaderApplicator(RemoteLoginInfo loginInfo) - { - remoteLoginInfo = loginInfo; - } - - /// - /// Add to the message inspectors for the channel - /// - /// The - /// The - public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) - { - var t = Type.GetType("Mono.Runtime"); - ICollection inspectors; - if (t != null) - { - var prop = clientRuntime.GetType() - .GetTypeInfo() - .GetDeclaredProperty("MessageInspectors"); - inspectors = (ICollection)prop - .GetValue(clientRuntime); - } - else - inspectors = clientRuntime.ClientMessageInspectors; - inspectors.Add(this); - } - - /// - /// Attach and to the - /// - /// The outgoing request - /// The - /// - public object BeforeSendRequest(ref Message request, IClientChannel channel) - { - request.Headers.Add(MessageHeader.CreateHeader("Username", "http://tempuri.org", remoteLoginInfo.Username)); - request.Headers.Add(MessageHeader.CreateHeader("Password", "http://tempuri.org", remoteLoginInfo.Password)); - return null; - } - - /// - /// Unused implementation of - /// - public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) - { - //intentionally left blank - } - - /// - /// Unused implementation of - /// - public void AfterReceiveReply(ref Message reply, object correlationState) - { - //intentionally left blank - } - - /// - /// Unused implementation of - /// - public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) - { - //intentionally left blank - } - - /// - /// Unused implementation of - /// - public void Validate(ServiceEndpoint endpoint) - { - //intentionally left blank - } - } -} diff --git a/legacy/V3/TGS.Interface/ChatSetupInfo.cs b/legacy/V3/TGS.Interface/ChatSetupInfo.cs deleted file mode 100644 index 91cde83a75..0000000000 --- a/legacy/V3/TGS.Interface/ChatSetupInfo.cs +++ /dev/null @@ -1,344 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace TGS.Interface -{ - /// - /// For setting up authentication no matter the chat provider - /// - [DataContract] - [KnownType(typeof(IRCSetupInfo))] - [KnownType(typeof(DiscordSetupInfo))] - public class ChatSetupInfo - { - const int AdminListIndex = 0; - const int AdminModeIndex = 1; - const int AdminChannelIndex = 2; - const int DevChannelIndex = 3; - const int WDChannelIndex = 4; - const int GameChannelIndex = 5; - const int ProviderIndex = 6; - const int EnabledIndex = 7; - /// - /// Starting index of which child classes should use to write their custom data to - /// - protected const int BaseIndex = 8; - /// - /// Set to if a child constructor should use the baseInfo parameter of to initialize it's property fields, otherwise - /// - protected readonly bool InitializeFields; - - /// - /// Raw access to the underlying data - /// - [DataMember] - public IList DataFields { get; protected set; } - - /// - /// Constructs a from optional - /// - /// The that this is for - /// Optional past data - /// The number of fields in this chat provider - protected internal ChatSetupInfo(ChatProvider provider, ChatSetupInfo baseInfo, int numFields) - { - numFields += BaseIndex; - InitializeFields = baseInfo == null || baseInfo.DataFields.Count != numFields; - - if (InitializeFields) - { - DataFields = new List(numFields); - for (var I = 0; I < numFields; ++I) - DataFields.Add(null); - - AdminList = new List(); - AdminChannels = new List(); - DevChannels = new List(); - GameChannels = new List(); - WatchdogChannels = new List(); - AdminsAreSpecial = false; - Enabled = false; - } - else - DataFields = baseInfo.DataFields; - Provider = provider; - Specialize(true); //to check we have a valid provider - } - - /// - /// Recreates as the correct child - /// - /// If , is returned provided is a valid - /// A new based on the type - ChatSetupInfo Specialize(bool checkOnly) - { - switch (Provider) - { - case ChatProvider.IRC: - if (!checkOnly) - return new IRCSetupInfo(this); - break; - case ChatProvider.Discord: - if (!checkOnly) - return new DiscordSetupInfo(this); - break; - default: - throw new Exception("Invalid provider!"); - } - return null; - } - - /// - /// Properly formats a name for the - /// - /// The to format - /// The formatted - protected virtual string SanitizeChannelName(string channel) - { - return Specialize(false).SanitizeChannelName(channel); - } - - /// - /// Sanitizes a list of - /// - /// A of strings - void SanitizeChannelNames(IList channelnames) - { - for (var I = 0; I < channelnames.Count; ++I) - - if (String.IsNullOrWhiteSpace(channelnames[I])) - { - channelnames.RemoveAt(I); - --I; - } - else - channelnames[I] = SanitizeChannelName(channelnames[I].Trim()); - } - - /// - /// Constructs a from a data list - /// - /// The data - public ChatSetupInfo(IList DeserializedData) - { - DataFields = DeserializedData; - Specialize(false); //ensure provider type is valid - } - /// - /// The list of admin entries - /// - public List AdminList - { - get { return JsonConvert.DeserializeObject>(DataFields[AdminListIndex]); } - set { DataFields[AdminListIndex] = JsonConvert.SerializeObject(value); } - } - /// - /// If AdminList corresponds to a Provider specific recognization method - /// - public bool AdminsAreSpecial - { - get { return Convert.ToBoolean(DataFields[AdminModeIndex]); } - set { DataFields[AdminModeIndex] = Convert.ToString(value); } - } - /// - /// The channels from which admin commands/messages can be sent/received - /// - public List AdminChannels - { - get { return JsonConvert.DeserializeObject>(DataFields[AdminChannelIndex]); } - set - { - SanitizeChannelNames(value); - DataFields[AdminChannelIndex] = JsonConvert.SerializeObject(value); - } - } - /// - /// The channels to which repo and compile messages are sent - /// - public List DevChannels - { - get { return JsonConvert.DeserializeObject>(DataFields[DevChannelIndex]); } - set - { - SanitizeChannelNames(value); - DataFields[DevChannelIndex] = JsonConvert.SerializeObject(value); - } - } - /// - /// The channels to which watchdog messages are sent - /// - public List WatchdogChannels - { - get { return JsonConvert.DeserializeObject>(DataFields[WDChannelIndex]); } - set - { - SanitizeChannelNames(value); - DataFields[WDChannelIndex] = JsonConvert.SerializeObject(value); - } - } - /// - /// The channels to which game messages are sent - /// - public List GameChannels - { - get { return JsonConvert.DeserializeObject>(DataFields[GameChannelIndex]); } - set - { - SanitizeChannelNames(value); - DataFields[GameChannelIndex] = JsonConvert.SerializeObject(value); - } - } - /// - /// If this chat provider is enabled - /// - public bool Enabled - { - get { return Convert.ToBoolean(DataFields[EnabledIndex]); } - set { DataFields[EnabledIndex] = Convert.ToString(value); } - } - - /// - /// The type of provider - /// - public ChatProvider Provider - { - get { return (ChatProvider)Convert.ToInt32(DataFields[ProviderIndex]); } - set { DataFields[ProviderIndex] = Convert.ToString((int)value); } - } - } - - /// - /// Chat provider for IRC. Admin entries should be user nicknames in normal mode or required channel flags in special mode - /// - [DataContract] - public sealed class IRCSetupInfo : ChatSetupInfo - { - const int URLIndex = 0; - const int PortIndex = 1; - const int NickIndex = 2; - const int AuthTargetIndex = 3; - const int AuthMessageIndex = 4; - const int AuthLevelIndex = 5; - const int FieldsLen = 6; - - /// - /// Construct IRC setup info from optional generic info. Defaults to TGS3 on rizons IRC server - /// - /// Optional generic info - public IRCSetupInfo(ChatSetupInfo baseInfo = null) : base(ChatProvider.IRC, baseInfo, FieldsLen) - { - if (!InitializeFields) - return; - - Nickname = "TGS3"; - URL = "irc.rizon.net"; - Port = 6667; - AuthTarget = ""; - AuthMessage = ""; - AdminsAreSpecial = true; - AuthLevel = IRCMode.Op; - } - - /// - protected override string SanitizeChannelName(string working) - { - if (working[0] != '#') - return "#" + working; - return working; - } - - /// - /// The port of the IRC server - /// - public ushort Port - { - get { return Convert.ToUInt16(DataFields[BaseIndex + PortIndex]); } - set { DataFields[BaseIndex + PortIndex] = value.ToString(); } - } - /// - /// The URL of the IRC server - /// - public string URL - { - get { return DataFields[BaseIndex + URLIndex]; } - set { DataFields[BaseIndex + URLIndex] = value; } - } - /// - /// The nickname of the IRC bot - /// - public string Nickname - { - get { return DataFields[BaseIndex + NickIndex]; } - set { DataFields[BaseIndex + NickIndex] = value; } - } - /// - /// The target for sending authentication messages - /// - public string AuthTarget - { - get { return DataFields[BaseIndex + AuthTargetIndex]; } - set { DataFields[BaseIndex + AuthTargetIndex] = value; } - } - /// - /// The authentication message - /// - public string AuthMessage - { - get { return DataFields[BaseIndex + AuthMessageIndex]; } - set { DataFields[BaseIndex + AuthMessageIndex] = value; } - } - /// - /// The minimum mode required to use admin bot commands when in special auth mode - /// - public IRCMode AuthLevel - { - get { return (IRCMode)Convert.ToInt32(DataFields[BaseIndex + AuthLevelIndex]); } - set { DataFields[BaseIndex + AuthLevelIndex] = Convert.ToString((int)value); } - } - } - - /// - /// Chat provider for Discord. Admin entires should be user ids in normal mode or group ids in special mode - /// - [DataContract] - public sealed class DiscordSetupInfo : ChatSetupInfo - { - const int BotTokenIndex = 0; - const int FieldsLen = 1; - /// - /// Construct Discord setup info from optional generic info. Default is not a valid discord bot tokent - /// - /// Optional generic info - public DiscordSetupInfo(ChatSetupInfo baseInfo = null) : base(ChatProvider.Discord, baseInfo, FieldsLen) - { - if (!InitializeFields) - return; - BotToken = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; //needless to say, this is fake - } - /// - protected override string SanitizeChannelName(string working) - { - working = working.Replace("<", "").Replace(">", "").Replace("&", ""); //filter out some stuff that can come in the copypasta - try - { - Convert.ToUInt64(working); - } - catch - { - throw new Exception("Invalid Discord channel ID!"); - } - return working; - } - - /// - /// The Discord bot token to use. See https://discordapp.com/developers/applications/me for registering bot accounts - /// - public string BotToken - { - get { return DataFields[BaseIndex + BotTokenIndex]; } - set { DataFields[BaseIndex + BotTokenIndex] = value; } - } - } -} diff --git a/legacy/V3/TGS.Interface/Client.cs b/legacy/V3/TGS.Interface/Client.cs deleted file mode 100644 index ef367d62e3..0000000000 --- a/legacy/V3/TGS.Interface/Client.cs +++ /dev/null @@ -1,318 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Net; -using System.Net.Security; -using System.Reflection; -using System.Security.Principal; -using System.ServiceModel; -using System.ServiceModel.Description; -using TGS.Interface.Components; - -namespace TGS.Interface -{ - /// - sealed public class Client : IClient - { - /// - /// Version of the - /// - public static readonly Version Version = Assembly.GetExecutingAssembly().GetName().Version; - - /// - public IServer Server => server; - - /// - public Version ServerVersion { get - { - lock (this) - if (_serverVersion == null) - { - string rawVersion; - //check ITGSService first for compatiblity reasons - try - { - rawVersion = GetComponent(null).Version(); - } - catch - { - rawVersion = GetComponent(null).Version(); - } - var splits = rawVersion.Split(' '); - _serverVersion = new Version(splits[splits.Length - 1].Substring(1)); - } - return _serverVersion; - } } - - /// - public string InstanceName { get; private set; } - - /// - public RemoteLoginInfo LoginInfo { get { return _loginInfo; } } - - /// - /// Backing field for - /// - readonly IServer server; - - /// - /// Backing field for - /// - readonly RemoteLoginInfo _loginInfo; - - /// - /// The - /// - Version _serverVersion; - - /// - /// Associated list of open s keyed by type name. A in this list may close or fault at any time. must be locked before being accessed - /// - IDictionary ChannelFactoryCache; - - /// - /// Sets the function called when a remote login fails due to the server having an invalid SSL cert - /// - /// The to be called when a remote login is attempted while the server posesses a bad certificate. Passed a of error information about the and should return if it the connection should be made anyway - public static void SetBadCertificateHandler(Func handler) - { - ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, error) => - { - string ErrorMessage; - switch (error) - { - case SslPolicyErrors.None: - return true; - case SslPolicyErrors.RemoteCertificateChainErrors: - ErrorMessage = "There are certificate chain errors."; - break; - case SslPolicyErrors.RemoteCertificateNameMismatch: - ErrorMessage = "The certificate name does not match."; - break; - case SslPolicyErrors.RemoteCertificateNotAvailable: - ErrorMessage = "The certificate doesn't exist in the trust store."; - break; - default: - ErrorMessage = "An unknown error occurred."; - break; - } - ErrorMessage = String.Format("The server's certificate failed to verify! Error: {0} Cert: {1}", ErrorMessage, cert.ToString()); - return handler(ErrorMessage); - }; - } - - /// - /// Construct an for a local connection - /// - public Client() - { - ChannelFactoryCache = new Dictionary(); - server = new Server(this); - } - - /// - /// Construct an for a remote connection - /// - /// The for a remote connection - public Client(RemoteLoginInfo loginInfo) : this() - { - if (!loginInfo.HasPassword) - throw new InvalidOperationException("password must be set on loginInfo!"); - _loginInfo = loginInfo; - } - - /// - public bool IsRemoteConnection { get { return LoginInfo != null; } } - - /// - /// Closes all s stored in and it - /// - public void Dispose() - { - lock (this) - { - if (ChannelFactoryCache == null) - return; - foreach (var I in ChannelFactoryCache) - { - var cf = I.Value; - try - { - cf.Close(); - } - catch - { - cf.Abort(); - } - } - ChannelFactoryCache = null; - } - } - - /// - public bool VersionMismatch(out string errorMessage) - { - if (ServerVersion.Major != Version.Major || ServerVersion.Minor != Version.Minor || ServerVersion.Build != Version.Build) //don't care about the patch level - { - errorMessage = String.Format("Version mismatch between interface version ({0}) and service version ({1}). Some functionality may crash this program.", Version, ServerVersion); - return true; - } - errorMessage = null; - return false; - } - - /// - /// Returns the requested component for the instance . This does not guarantee a successful connection. s created this way are recycled for minimum latency and bandwidth usage - /// - /// The component to retrieve - /// The name of the to use - /// The correct component - internal T GetComponent(string instanceName) - { - var actualToT = typeof(T); - var tot = actualToT.Name; - if (instanceName != null) - tot = instanceName + tot; - ChannelFactory cf; - - lock (this) - { - if (ChannelFactoryCache == null) - throw new ObjectDisposedException(GetType().Name); - if (ChannelFactoryCache.ContainsKey(tot)) - try - { - cf = ((ChannelFactory)ChannelFactoryCache[tot]); - if (cf.State != CommunicationState.Opened) - throw new Exception(); - return cf.CreateChannel(); - } - catch - { - ChannelFactoryCache[tot].Abort(); - ChannelFactoryCache.Remove(tot); - } - cf = CreateChannel(instanceName); - ChannelFactoryCache[tot] = cf; - } - return cf.CreateChannel(); - } - - /// - /// Directly creates a for without caching. This should be eventually closed by the caller - /// - /// The component of the channel to be created - /// The instance to connect to, if any - /// The correct - ChannelFactory CreateChannel(string instanceName) - { - var accessPath = instanceName == null ? Definitions.MasterInterfaceName : String.Format("{0}/{1}", Definitions.InstanceInterfaceName, instanceName); - if (!IsRemoteConnection) - return CreateLocalChannel(instanceName, accessPath); - return CreateRemoteChannel(instanceName, accessPath); - } - - //NOTE: This needs to be kept seperate from CreateRemoteChannel because not all our client implementations have NetNamedPipeBinding and attempting to call this function on those platforms will throw an exception - - /// - /// Directly creates a for on a local connection without caching. This should be eventually closed by the caller - /// - /// The component of the channel to be created - /// The instance to connect to, if any - /// The URL of the interface to connect to - /// The correct - ChannelFactory CreateLocalChannel(string instanceName, string accessPath) - { - var interfaceName = typeof(T).Name; - var res2 = new ChannelFactory( - new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = Definitions.TransferLimitLocal }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", accessPath, interfaceName))); //10 megs - res2.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; - return res2; - } - - /// - /// Directly creates a for on a remote connection without caching. This should be eventually closed by the caller - /// - /// The component of the channel to be created - /// The instance to connect to, if any - /// The URL of the interface to connect to - /// The correct - ChannelFactory CreateRemoteChannel(string instanceName, string accessPath) - { - //tls memes - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - //okay we're going over - var binding = new BasicHttpsBinding() - { - SendTimeout = new TimeSpan(0, 0, 40), - MaxReceivedMessageSize = Definitions.TransferLimitRemote - }; - - var interfaceName = typeof(T).Name; - var requireAuth = interfaceName != typeof(ITGConnectivity).Name; - var url = String.Format("https://{0}:{1}/{2}/{3}", LoginInfo.IP, LoginInfo.Port, accessPath, interfaceName); - var address = new EndpointAddress(url); - var res = new ChannelFactory(binding, address); - if (requireAuth) - { - var applicator = new AuthenticationHeaderApplicator(LoginInfo); - var t = Type.GetType("Mono.Runtime"); - KeyedCollection behaviours; - if (t != null) - { - var prop = res.Endpoint.GetType() - .GetTypeInfo() - .GetDeclaredProperty("Behaviors"); - behaviours = (KeyedCollection)prop - .GetValue(res.Endpoint); - } - else - behaviours = res.Endpoint.EndpointBehaviors; - behaviours.Add(applicator); - } - return res; - } - - /// - public ConnectivityLevel ConnectionStatus() - { - return ConnectionStatus(out string unused); - } - - /// - public ConnectivityLevel ConnectionStatus(out string error) - { - try - { - GetComponent(null).VerifyConnection(); - } - catch (Exception e) - { - error = e.ToString(); - return ConnectivityLevel.None; - } - - try - { - GetComponent(null).Version(); - } - catch(Exception e) - { - error = e.ToString(); - return ConnectivityLevel.Connected; - } - - try - { - GetComponent(null).Version(); - error = null; - return ConnectivityLevel.Administrator; - } - catch(Exception e) - { - error = e.ToString(); - return ConnectivityLevel.Authenticated; - } - } - } -} diff --git a/legacy/V3/TGS.Interface/Command.cs b/legacy/V3/TGS.Interface/Command.cs deleted file mode 100644 index ac64ab1914..0000000000 --- a/legacy/V3/TGS.Interface/Command.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; - -namespace TGS.Interface -{ - /// - /// Helper for creating a text tree - /// - public abstract class Command - { - /// - /// Exit codes for s - /// - public enum ExitCode : int - { - /// - /// The ran successfully - /// - Normal = 0, - /// - /// The connection to the service was interrupted during the - /// - ConnectionError = 1, - /// - /// Invalid parameters for - /// - BadCommand = 2, - /// - /// The command failed due to conditions on the service - /// - ServerError = 3, - } - /// - /// Proc that will show a message to the invoker. Do not call directly, use instead - /// - public static ThreadLocal> OutputProcVar = new ThreadLocal>(); - /// - /// Write output to the invoker - /// - /// The output to display - protected static void OutputProc(string message) - { - OutputProcVar.Value(message); - } - /// - /// The text that invokes this . Set in constructor - /// - public string Keyword { get; protected set; } - /// - /// The number of parameters this requires. Set in Constructor - /// - public int RequiredParameters { get; protected set; } - /// - /// Caller of , can be used to modify the root behaviour of the - /// - /// List of parameters passed to the - /// An describing the execution of the - public virtual ExitCode DoRun(IList parameters) - { - return Run(parameters); - } - /// - /// Override to do the actions of the - /// - /// List of parameters passed to the . Guaranteed to have at least non-empty/whitespace entries - /// An describing the execution of the - protected abstract ExitCode Run(IList parameters); - /// - /// Prints usage text of the to the invoker - /// - public virtual void PrintHelp() - { - var argstr = GetArgumentString(); - OutputProc(String.Format("{0} {1}- {2}", Keyword, argstr.Length > 0 ? argstr + " " : "", GetHelpText())); - } - /// - /// Override to add argument text to the - /// Format is <required> <arguments> [optional] [arguments] - /// - /// Formatted argument text for the - public virtual string GetArgumentString() - { - return ""; - } - /// - /// Override to add usage text to the - /// - /// Formatted usage text for the - public abstract string GetHelpText(); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Administration.cs b/legacy/V3/TGS.Interface/Components/Administration.cs deleted file mode 100644 index 884c5b725a..0000000000 --- a/legacy/V3/TGS.Interface/Components/Administration.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Manage the group that is used to access the service, can only be used by an administrator - /// - [ServiceContract] - public interface ITGAdministration - { - /// - /// Returns the name of the windows group allowed to use the service other than administrator - /// - /// The name of the windows group allowed to use the service other than administrator, "ADMIN" if it's unset, on failure - [OperationContract] - string GetCurrentAuthorizedGroup(); - - /// - /// Searches the windows machine for the group named , sets it as the authorized group if it's found - /// - /// The name of the windows group to search for or null to clear the setting - /// The name of the windows group that is now authorized to use the service on success, on failure, "ADMIN" on clearing - [OperationContract] - string SetAuthorizedGroup(string groupName); - - /// - /// Renames the current static folder to a backup name and recreates is from the current repo using TGS3.json - /// - /// on success, error message on failure - [OperationContract] - string RecreateStaticFolder(); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Byond.cs b/legacy/V3/TGS.Interface/Components/Byond.cs deleted file mode 100644 index 7563000999..0000000000 --- a/legacy/V3/TGS.Interface/Components/Byond.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - - /// - /// For managing the BYOND installation the server runs - /// - [ServiceContract] - public interface ITGByond - { - /// - /// Gets the current status of any BYOND updates - /// - /// The current status of the byond updater - [OperationContract] - ByondStatus CurrentStatus(); - - /// - /// updates the used byond version to that of version .. The change won't take place until DD reboots. Calls if blocked by a running DD instance. Runs asyncronously, use to check progress - /// - /// Major BYOND version. E.g. 511 - /// Minor BYOND version. E.g. 1381 - /// if the update started, if another operation was in progress or DreamDaemon is running - [OperationContract] - bool UpdateToVersion(int major, int minor); - - /// - /// Check the last update error. Checking this will clear the value - /// - /// The last update error, if any. otherwise. - [OperationContract] - string GetError(); - - /// - /// Get the currently installed version as a string formatted as Major.Minor - /// - /// The type of version to retrieve - /// if no version is detected, the version string otherwise - [OperationContract] - string GetVersion(ByondVersion type); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Chat.cs b/legacy/V3/TGS.Interface/Components/Chat.cs deleted file mode 100644 index 5333e4e649..0000000000 --- a/legacy/V3/TGS.Interface/Components/Chat.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Interface for handling chat bot - /// - [ServiceContract] - public interface ITGChat - { - /// - /// Sets a chat provider - /// - /// The info to set - [OperationContract] - string SetProviderInfo(ChatSetupInfo info); - /// - /// Returns for all s - /// - /// A list of all s - [OperationContract] - IList ProviderInfos(); - - /// - /// Checks connection status - /// - /// The type of provider to check if connected - /// if connected, otherwise - [OperationContract] - bool Connected(ChatProvider providerType); - - /// - /// Reconnect a specific to it's chat service - /// - /// The type of provider to reconnect - /// on success, error message on failure - [OperationContract] - string Reconnect(ChatProvider providerType); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Compiler.cs b/legacy/V3/TGS.Interface/Components/Compiler.cs deleted file mode 100644 index ce87ff125e..0000000000 --- a/legacy/V3/TGS.Interface/Components/Compiler.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// For managing the Game A/B/Live folders, compiling, and hotswapping them - /// - [ServiceContract] - public interface ITGCompiler - { - /// - /// Sets up the symlinks for hotswapping game code. This will reset everything in the Game folder. Requires the repository to be set up and locks it once the compilation stage starts. Runs asyncronously from this call - /// - /// if the operation began, if it could not start - [OperationContract] - bool Initialize(); - - /// - /// Does all the necessary actions to take the revision currently in the repository and compile it to be run on the next server reboot. Requires BYOND to be set up and the to return . Runs asyncronously - /// - /// If no message for compilation start will be printed - /// if the operation began, if it could not start - [OperationContract] - bool Compile(bool silent = false); - - /// - /// Cancels the current compilation - /// - /// on success, error message on failure - [OperationContract] - string Cancel(); - - /// - /// Returns the current compiler status - /// - /// The current compiler status - [OperationContract] - CompilerStatus GetStatus(); - - /// - /// Returns the error message of the last operation. Reading this will clear the stored value - /// - /// the error message of the last operation if it failed or if it succeeded - [OperationContract] - string CompileError(); - - /// - /// Returns the relative path of the dme the compiler will look for without the .dme part - /// - /// The relative path of the dme the compiler will look for without the .dme part - [OperationContract] - string ProjectName(); - - /// - /// Sets the relative path of the dme the compiler will look for without the .dme part - /// - /// The relative path of the dme the compiler will look for without the .dme part - [OperationContract] - void SetProjectName(string projectName); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Config.cs b/legacy/V3/TGS.Interface/Components/Config.cs deleted file mode 100644 index 2e9409228e..0000000000 --- a/legacy/V3/TGS.Interface/Components/Config.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections.Generic; -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// For modifying the in game config - /// Most if not all of these will not apply until the next server reboot - /// - [ServiceContract] - public interface ITGConfig - { - - /// - /// Returns the file contents of the specified server directory - /// Subdirectories will be prefixed with '/' - /// - /// Subdirectory to enumerate, enumerates the root directory if null - /// null on success, error message on failure - /// This will be true if error is set to a message that indicates the current user does not have access to the specified file - /// A list of files in the enumerated static directory on success, null on failure - [OperationContract] - IList ListStaticDirectory(string subpath, out string error, out bool unauthorized); - - /// - /// Read from a static file - /// - /// The path from the Static dir. E.g. config/config.txt - /// if true, the file will be read from the repository instead of the static dir - /// null on success, error message on failure - /// This will be true if error is set to a message that indicates the current user does not have access to the specified file - /// The full text of the file on success, null on failure - /// Along with implied disconnect exceptions, if the file exceeds transfer limits - [OperationContract] - string ReadText(string staticRelativePath, bool repo, out string error, out bool unauthorized); - - /// - /// Write to a static file - /// - /// The path from the Static dir. E.g. config/config.txt - /// The full text of the config file - /// The original data that the client knows about. Set to null to skip checks - /// This will be true if error is set to a message that indicates the current user does not have access to the specified file - /// null on success, error message on failure - /// Along with implied disconnect exceptions, if the file exceeds transfer limits - [OperationContract] - string WriteText(string staticRelativePath, string data, string originalData, out bool unauthorized); - - /// - /// Deletes the target static file - /// - /// The path from the Static dir. E.g. config/config.txt - /// This will be true if error is set to a message that indicates the current user does not have access to the specified file - /// null on success, error message on failure - [OperationContract] - string DeleteFile(string staticRelativePath, out bool unauthorized); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Connectivity.cs b/legacy/V3/TGS.Interface/Components/Connectivity.cs deleted file mode 100644 index d7c5b57f64..0000000000 --- a/legacy/V3/TGS.Interface/Components/Connectivity.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Used for testing connections to the service without authentication - /// - [ServiceContract] - public interface ITGConnectivity - { - /// - /// Does nothing on the server end, but if the call completes, you can be sure you are connected. WCF won't throw until you try until you actually use the API - /// - [OperationContract] - void VerifyConnection(); - } -} diff --git a/legacy/V3/TGS.Interface/Components/DreamDaemon.cs b/legacy/V3/TGS.Interface/Components/DreamDaemon.cs deleted file mode 100644 index 0892e3f8a1..0000000000 --- a/legacy/V3/TGS.Interface/Components/DreamDaemon.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - - /// - /// Interface for managing the actual BYOND game server - /// - [ServiceContract] - public interface ITGDreamDaemon - { - /// - /// Gets the status of DreamDaemon - /// - /// The appropriate - [OperationContract] - DreamDaemonStatus DaemonStatus(); - - /// - /// Returns a human readable string of the current server status - /// - /// If , the status will include the server's current visibility and security levels - /// A human readable of the current server status - [OperationContract] - string StatusString(bool includeMetaInfo); - - /// - /// Check if a call to will fail. Of course, be aware of race conditions with other interfaces - /// - /// The error that would occur, otherwise - [OperationContract] - string CanStart(); - - /// - /// Starts the server if it isn't running - /// - /// on success or error message on failure - [OperationContract] - string Start(); - - /// - /// Immediately kills the server - /// - /// on success or error message on failure - [OperationContract] - string Stop(); - - /// - /// Immediately kills and restarts the server - /// - /// on success or error message on failure - [OperationContract] - string Restart(); - - /// - /// Restart the server after the currently running world reboots. Has no effect if the server isn't running - /// - [OperationContract] - void RequestRestart(); - - /// - /// Stop the server after the currently running world reboots. Has no effect if the server isn't running - /// - [OperationContract] - void RequestStop(); - - /// - /// Get the configured (not necessarily running) security level - /// - /// The configured (not necessarily running) - [OperationContract] - DreamDaemonSecurity SecurityLevel(); - - /// - /// Sets the security level of the server. Requires server reboot to apply. Calls . Note that anything higher than Trusted will disable interop from DD - /// - /// The new security level - /// if the change was immediately applied, otherwise and a call to was made - [OperationContract] - bool SetSecurityLevel(DreamDaemonSecurity level); - - /// - /// Get the configured port. Not necessarily the running port if it has since changed - /// - /// The configured port - [OperationContract] - ushort Port(); - - /// - /// Set the port to host DD on. Requires reboot to apply. Calls . - /// - /// The new port - [OperationContract] - void SetPort(ushort new_port); - - /// - /// Check if the watchdog will start when the service starts - /// - /// if autostart is enabled, otherwise - [OperationContract] - bool Autostart(); - - /// - /// Set the autostart config - /// - /// to start the watchdog with the service, to disable that functionality - [OperationContract] - void SetAutostart(bool on); - - /// - /// Check if the BYOND webclient is currently enabled for the server - /// - /// if the webclient is enabled, otherwise - [OperationContract] - bool Webclient(); - - /// - /// Set the webclient config. Calls - /// - /// to enable the byond webclient for the server, otherwise - [OperationContract] - void SetWebclient(bool on); - - /// - /// Checks if a server stop has been requested - /// - /// if has been called since the last server start, otherwise - [OperationContract] - bool ShutdownInProgress(); - - /// - /// Sends a message to everyone on the server - /// - /// The message to send - /// on success, error message on failure - [OperationContract] - string WorldAnnounce(string msg); - - /// - /// Returns the number of connected players. Requires game to use API version >= 3.1.0.1 - /// - /// The number of connected players or -1 on error - [OperationContract] - int PlayerCount(); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Instance.cs b/legacy/V3/TGS.Interface/Components/Instance.cs deleted file mode 100644 index 1cc79e153b..0000000000 --- a/legacy/V3/TGS.Interface/Components/Instance.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Metadata for a server instance - /// - [ServiceContract] - public interface ITGInstance - { - /// - /// Return the directory of the server on the host machine - /// - /// The path to the directory on success, null on failure - [OperationContract] - string ServerDirectory(); - - /// - /// Retrieve's the service's version - /// - /// The service's version - [OperationContract] - string Version(); - } -} diff --git a/legacy/V3/TGS.Interface/Components/InstanceManager.cs b/legacy/V3/TGS.Interface/Components/InstanceManager.cs deleted file mode 100644 index f148acb644..0000000000 --- a/legacy/V3/TGS.Interface/Components/InstanceManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Used for managing s - /// - [ServiceContract] - public interface ITGInstanceManager - { - /// - /// Creates a new - /// - /// The name of the instance - /// The path to the instance - /// on success, error message on failure - [OperationContract] - string CreateInstance(string Name, string path); - - /// - /// Registers an existing server instance - /// - /// The path to the instance - /// on success, error message on failure - [OperationContract] - string ImportInstance(string path); - - /// - /// Checks if an instance is online - /// - /// The name of the instance - /// if the Instance exists and is online, otherwise - [OperationContract] - bool InstanceEnabled(string Name); - - /// - /// Sets an instance's enabled status - /// - /// The instance whom's status should be changed - /// to enable the instance, to disable it - /// on success, error message on failure - [OperationContract] - string SetInstanceEnabled(string Name, bool enabled); - - /// - /// Renames an instance, this will restart the instance if it is enabled - /// - /// The current name of the instance - /// The new name of the instance - /// on success, error message on failure - [OperationContract] - string RenameInstance(string name, string new_name); - - /// - /// Disables and unregisters an instance, allowing the folder and data to be manipulated manually - /// - /// The instance to detach - /// on success, error message on failure - [OperationContract] - string DetachInstance(string name); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Interop.cs b/legacy/V3/TGS.Interface/Components/Interop.cs deleted file mode 100644 index 80dc2c7c7c..0000000000 --- a/legacy/V3/TGS.Interface/Components/Interop.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Used by DreamDaemon to access the interop API with call()(). Restrictions are in place so that only a DreamDaemon instance launched by the service can use this API - /// - [ServiceContract] - public interface ITGInterop - { - /// - /// Called from /world/ExportService(command) - /// - /// The command to run - /// on success, on failure - [OperationContract] - bool InteropMessage(string command); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Landing.cs b/legacy/V3/TGS.Interface/Components/Landing.cs deleted file mode 100644 index da7cd28b9e..0000000000 --- a/legacy/V3/TGS.Interface/Components/Landing.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Used for general authentication and listing s - /// - [ServiceContract] - public interface ITGLanding - { - /// - /// Retrieve's the service's version - /// - /// The service's version - [OperationContract] - string Version(); - - /// - /// List instances that the caller can access - /// - /// A of instance names relating to their paths - [OperationContract] - IList ListInstances(); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Repository.cs b/legacy/V3/TGS.Interface/Components/Repository.cs deleted file mode 100644 index 6a518fca0a..0000000000 --- a/legacy/V3/TGS.Interface/Components/Repository.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System.Collections.Generic; -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Interface for managing the code repository - /// - [ServiceContract] - public interface ITGRepository - { - /// - /// If the repo is currently undergoing an operation - /// - /// if the repo is busy, otherwise - [OperationContract] - bool OperationInProgress(); - - /// - /// Gets the progress of repository operations, not all operations are supported - /// - /// A value between 0 and 100 inclusive representing the progress of the current operation or -1 if the operation cannot be monitored - [OperationContract] - int CheckoutProgress(); - - /// - /// Check if the repository is valid, if not must be called - /// - /// if the repository is valid, otherwise - [OperationContract] - bool Exists(); - - /// - /// Deletes whatever may be left over and clones the repo at and checks out . Will move config and data dirs to a backup location if they exist. Runs asyncronously - /// - /// The address of the repo to clone. If ssh protocol is used, private_key.txt and public_key.txt must exist in the server RepoKey directory. - /// The branch of the repo to checkout - /// on success, error message on failure - [OperationContract] - string Setup(string remote, string branch = "master"); - - /// - /// Gets the sha of the current HEAD - /// - /// If set to true and HEAD is currently a branch, will instead return the sha of the tracked remote branch if it exists - /// on success, error message on failure - /// The sha of the current HEAD on success, on failure - [OperationContract] - string GetHead(bool useTracked, out string error); - - /// - /// Gets the name of the current branch - /// - /// on success, error message on failure - /// The name of the current branch on success, on failure - [OperationContract] - string GetBranch(out string error); - - /// - /// Gets the url of the current origin - /// - /// on success, error message on failure - /// The url of the current origin on success, on failure - [OperationContract] - string GetRemote(out string error); - - /// - /// Hard checks out the passed object name - /// - /// The branch, commit, or tag to checkout - /// on success, error message on failure - [OperationContract] - string Checkout(string objectName); - - /// - /// Fetches the origin and merges it into the current branch - /// - /// If , the operation will perform a hard reset instead of a merge - /// on success, error message on failure - [OperationContract] - string Update(bool reset); - - /// - /// Runs git reset --hard - /// - /// Changes command to git reset --hard origin/branch_name if - /// on success, error message on failure - [OperationContract] - string Reset(bool tracked); - - /// - /// Merges the target pull request into the current branch if the remote is a github repository - /// - /// The github pull request number in the remote repository - /// The SHA of the pull request to merge - /// Suppresses chat messages if - /// on success, error message on failure - [OperationContract] - string MergePullRequest(int PRnumber, string atSHA = null, bool silent = false); - - /// - /// Merges the target pull requests into the current branch if the remote is a github repository - /// - /// of s containing pull request numbers and shas in the remote repository - /// Suppresses chat messages if - /// on success, list of error messages if any fail failure. Those with indexes of those that succeed will have null entries - [OperationContract] - IEnumerable MergePullRequests(IEnumerable pullRequestInfos, bool silent = false); - - /// - /// Get the currently merged pull requests. Note that switching branches will delete this list and switching back won't restore it - /// - /// on success, error message on failure - /// A of - [OperationContract] - List MergedPullRequests(out string error); - - /// - /// Gets the name of the configured git committer - /// - /// The name of the configured git committer - [OperationContract] - string GetCommitterName(); - - /// - /// Sets the name of the configured git committer - /// - /// The name to set - [OperationContract] - void SetCommitterName(string newName); - - /// - /// Gets the email of the configured git committer - /// - /// The email of the configured git committer - [OperationContract] - string GetCommitterEmail(); - - /// - /// Sets the email of the configured git committer - /// - /// The email to set - [OperationContract] - void SetCommitterEmail(string newEmail); - - /// - /// Updates the html changelog - /// - /// on success, error on failure - /// The output of the python script - [OperationContract] - string GenerateChangelog(out string error); - - /// - /// Pushes the paths listed in TGS3.json to the currentl git remote. No other commit differences may exist for this function to succeed - /// - /// on success, error on failure - [OperationContract] - string SynchronizePush(); - - /// - /// List the tagged commits of the repo at which compiles took place - /// - /// on success, error message on failure - /// A of tag name -> commit on success, on failure - [OperationContract] - IDictionary ListBackups(out string error); - - /// - /// Updates the cached TGS3.json to the repo's version. Compiles will not succeed if these two to not match - /// - /// on success, error message on failure - [OperationContract] - string UpdateTGS3Json(); - - /// - /// (De)Activate and set the interval for the automatic server updater - /// - /// Interval to check for updates in minutes, disables if 0 - [OperationContract] - void SetAutoUpdateInterval(ulong newInterval); - - /// - /// Get the current autoupdate interval - /// - /// The current auto update interval or 0 if it's disabled - [OperationContract] - ulong AutoUpdateInterval(); - - /// - /// Check if we push a temporary branch to the remote when we make testmerge commits - /// - /// if we publish testmerge commits to the remote, otherwise - [OperationContract] - bool PushTestmergeCommits(); - - /// - /// Set if we push a temporary branch to the remote when we make testmerge commits - /// - /// if we testmerge commits should be published to the remote, otherwise - [OperationContract] - void SetPushTestmergeCommits(bool newValue); - } -} diff --git a/legacy/V3/TGS.Interface/Components/Service.cs b/legacy/V3/TGS.Interface/Components/Service.cs deleted file mode 100644 index 17fc5f7096..0000000000 --- a/legacy/V3/TGS.Interface/Components/Service.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.ServiceModel; - -namespace TGS.Interface.Components -{ - /// - /// Interface for managing the service - /// - [ServiceContract] - public interface ITGSService - { - - /// - /// Next stop of the service will not close DD and sets a flag for it to reattach once it restarts - /// - [OperationContract] - void PrepareForUpdate(); - - /// - /// Get the port used for remote operation - /// - /// The port used for remote operation - [OperationContract] - ushort RemoteAccessPort(); - - /// - /// Set the port used for remote operation - /// Requires a service restart to take effect - /// - /// The new port to use for remote operation - /// null on success, error message on failure - [OperationContract] - string SetRemoteAccessPort(ushort port); - - /// - /// Retrieve's the service's version - /// - /// The service's version - [OperationContract] - string Version(); - - /// - /// Sets the path to the python 2.7 installation - /// - /// The new path - /// if the path exists, otherwise - [OperationContract] - bool SetPythonPath(string path); - - /// - /// Gets the path to the python 2.7 installation - /// - /// The path to the python 2.7 installation - [OperationContract] - string PythonPath(); - } -} diff --git a/legacy/V3/TGS.Interface/Definitions.cs b/legacy/V3/TGS.Interface/Definitions.cs deleted file mode 100644 index 05cc952c33..0000000000 --- a/legacy/V3/TGS.Interface/Definitions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using TGS.Interface.Components; - -namespace TGS.Interface -{ - /// - /// Contains constants for the interface - /// - public static class Definitions - { - /// - /// The maximum message size to and from a local server - /// - public const long TransferLimitLocal = Int32.MaxValue; //2GB can't go higher - - /// - /// The maximum message size to and from a remote server - /// - public const long TransferLimitRemote = 10485760; //10 MB - - /// - /// Base name of communication URLs - /// - public const string MasterInterfaceName = "TGStationServerService"; - /// - /// Base name of instance URLs - /// - public const string InstanceInterfaceName = MasterInterfaceName + "/Instance"; - } -} diff --git a/legacy/V3/TGS.Interface/Enumerations.cs b/legacy/V3/TGS.Interface/Enumerations.cs deleted file mode 100644 index caca32e31b..0000000000 --- a/legacy/V3/TGS.Interface/Enumerations.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; - -namespace TGS.Interface -{ - /// - /// Description of the connectivity level to an or the - /// - [Flags] - public enum ConnectivityLevel - { - /// - /// The connection could not be made, either a communication error occurred or the specified does not exist - /// - None = 0, - /// - /// The connection could be made - /// - Connected = 1, - /// - /// The connected user is authenticated - /// - Authenticated = 2 | Connected, - /// - /// The connected user is an administrator - /// - Administrator = 4 | Authenticated, - } - - /// - /// The status of a BYOND update job - /// - public enum ByondStatus - { - /// - /// No byond update in progress - /// - Idle, - /// - /// Preparing to update - /// - Starting, - /// - /// Revision is downloading - /// - Downloading, - /// - /// Revision is deflating - /// - Staging, - /// - /// Revision is ready and waiting for DreamDaemon reboot - /// - Staged, - /// - /// Revision is being applied - /// - Updating, - /// - /// Running game code is being recompiled under staged update - /// - CompilingStaged, - } - /// - /// Type of byond version - /// - public enum ByondVersion - { - /// - /// The highest version from http://www.byond.com/download/build/LATEST/ - /// - Latest, - /// - /// The version in the staging directory - /// - Staged, - /// - /// The installed version - /// - Installed, - } - /// - /// The type of chat provider - /// - public enum ChatProvider : int - { - /// - /// IRC chat provider - /// - IRC = 0, - /// - /// Discord chat provider - /// - Discord = 1, - } - - /// - /// Supported irc permission modes - /// - public enum IRCMode : int - { - /// - /// + - /// - Voice, - /// - /// % - /// - Halfop, - /// - /// @ - /// - Op, - /// - /// ~ - /// - Owner, - } - /// - /// The status of the compiler - /// - public enum CompilerStatus - { - /// - /// Game folder is broken or does not exist - /// - Uninitialized, - /// - /// Game folder is being created - /// - Initializing, - /// - /// Game folder is setup, does not imply the dmb is compiled - /// - Initialized, - /// - /// Game is being compiled - /// - Compiling, - } - - /// - /// The status of the DD instance - /// - public enum DreamDaemonStatus - { - /// - /// Server is not running - /// - Offline, - /// - /// Server is being rebooted - /// - HardRebooting, - /// - /// Server is running - /// - Online, - } - - /// - /// DreamDaemon's security level - /// - public enum DreamDaemonSecurity - { - /// - /// Server is unrestricted in terms of file access and shell commands - /// - Trusted = 0, - /// - /// Server will not be able to run shell commands or access files outside it's working directory - /// - Safe, - /// - /// Server will not be able to run shell commands or access anything but temporary files - /// - Ultrasafe - } -} diff --git a/legacy/V3/TGS.Interface/Helpers.cs b/legacy/V3/TGS.Interface/Helpers.cs deleted file mode 100644 index a7780a1aba..0000000000 --- a/legacy/V3/TGS.Interface/Helpers.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Security.Cryptography; -using System.Text; - -namespace TGS.Interface -{ - /// - /// Helper functions used across the server suite - /// - public static class Helpers - { - /// - /// Takes some and returns an encrypted version along with the required to decrypt it - /// - /// The to encrypt - /// The entropy required the decrypt the ciphertext - /// Ciphertext for the - public static string EncryptData(string cleartext, out string entropy) - { - // Generate additional entropy (will be used as the Initialization vector) - byte[] bentropy = new byte[20]; - using (var rng = new RNGCryptoServiceProvider()) - rng.GetBytes(bentropy); - - byte[] ciphertext = ProtectedData.Protect(Encoding.UTF8.GetBytes(cleartext), bentropy, DataProtectionScope.CurrentUser); - - entropy = Convert.ToBase64String(bentropy, 0, bentropy.Length); - return Convert.ToBase64String(ciphertext, 0, ciphertext.Length); - } - - /// - /// Takes ciphertext and entropy from and returns the cleartext. Note that this only works if the OS user of the program is the same one that called - /// - /// A return value from a previous call to - /// The entropy parameter from the previous call to that returned - /// The decrypted on sucess or on failure - public static string DecryptData(string ciphertext, string entropy) - { - try - { - return Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(ciphertext), Convert.FromBase64String(entropy), DataProtectionScope.CurrentUser)); - } - catch - { - return null; - } - } - } -} diff --git a/legacy/V3/TGS.Interface/IClient.cs b/legacy/V3/TGS.Interface/IClient.cs deleted file mode 100644 index 02394200fa..0000000000 --- a/legacy/V3/TGS.Interface/IClient.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; - -namespace TGS.Interface -{ - /// - /// Main for communicating the - /// - public interface IClient : IDisposable - { - /// - /// The the connects to - /// - IServer Server { get; } - - /// - /// The for the . Is for local connections - /// - RemoteLoginInfo LoginInfo { get; } - - /// - /// Checks if the is setup for a remote connection - /// - bool IsRemoteConnection { get; } - - /// - /// Returns if the interface being used to connect to a service does not have the same release version as the service - /// - /// An error message to display to the user should this function return - /// if the interface being used to connect to a service does not have the same release version as the service - bool VersionMismatch(out string errorMessage); - - /// - /// See without the error argument - /// - ConnectivityLevel ConnectionStatus(); - - /// - /// Used to test if the is avaiable on the target machine. Note that state can change at any time and any call into the may throw an exception because of communcation errors - /// - /// String of the error that prevented an elevated connectivity level - /// The apporopriate - ConnectivityLevel ConnectionStatus(out string error); - } -} diff --git a/legacy/V3/TGS.Interface/IInstance.cs b/legacy/V3/TGS.Interface/IInstance.cs deleted file mode 100644 index 66ca33e94f..0000000000 --- a/legacy/V3/TGS.Interface/IInstance.cs +++ /dev/null @@ -1,55 +0,0 @@ -using TGS.Interface.Components; - -namespace TGS.Interface -{ - /// - /// Wrapper for components - /// - public interface IInstance : ITGInstance - { - /// - /// Get the for the - /// - InstanceMetadata Metadata { get; } - - /// - /// The component. Will be if the connected user is not an administrator of the - /// - ITGAdministration Administration { get; } - - /// - /// The component - /// - ITGByond Byond { get; } - - /// - /// The component - /// - ITGChat Chat { get; } - - /// - /// The component - /// - ITGCompiler Compiler { get; } - - /// - /// The component - /// - ITGConfig Config { get; } - - /// - /// The component - /// - ITGDreamDaemon DreamDaemon { get; } - - /// - /// The component - /// - ITGInterop Interop { get; } - - /// - /// The component - /// - ITGRepository Repository { get; } - } -} diff --git a/legacy/V3/TGS.Interface/IServer.cs b/legacy/V3/TGS.Interface/IServer.cs deleted file mode 100644 index a81c6de744..0000000000 --- a/legacy/V3/TGS.Interface/IServer.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using TGS.Interface.Components; - -namespace TGS.Interface -{ - /// - /// Wrapper representing a - /// - public interface IServer - { - /// - /// The of the - /// - Version Version { get; } - - /// - /// Get the s the contains that the current user can access and connect to - /// - IEnumerable Instances { get; } - - /// - /// The component. Will be if the connected user is not an administrator of the - /// - ITGInstanceManager InstanceManager { get; } - - /// - /// The component. Will be if the connected user is not an administrator of the - /// - ITGSService Management { get; } - - /// - /// Gets the specified without connectivity checks - /// - /// The name of the to get - /// The named on success, on failure - IInstance GetInstance(string name); - } -} diff --git a/legacy/V3/TGS.Interface/Instance.cs b/legacy/V3/TGS.Interface/Instance.cs deleted file mode 100644 index 31d2c31a44..0000000000 --- a/legacy/V3/TGS.Interface/Instance.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Linq; -using TGS.Interface.Components; - -namespace TGS.Interface -{ - /// - sealed class Instance : IInstance - { - /// - public InstanceMetadata Metadata - { - get - { - if (!metadata.Enabled) - //metadata needs populating - metadata = serverInterface.GetComponent(null).ListInstances().Where(x => x.Name == metadata.Name).First(); - return metadata; - } - } - - /// - /// Whether or not the current user is known to be an administrator of the - /// - bool UserIsAdministrator - { - get - { - lock (this) - { - if (isAdministrator) - return true; - try - { - serverInterface.GetComponent(metadata.Name).GetCurrentAuthorizedGroup(); - isAdministrator = true; - return true; - } - catch - { - return false; - } - } - } - } - - /// - /// The backing - /// - readonly Client serverInterface; - /// - /// The name of the - /// - InstanceMetadata metadata; - /// - /// Backing field for - /// - bool isAdministrator; - - /// - /// Construct an - /// - /// The to use - /// The for the - public Instance(Client _serverInterface, InstanceMetadata _metadata) - { - serverInterface = _serverInterface; - metadata = _metadata; - if (metadata.Enabled) - //run a connectivity check - serverInterface.GetComponent(metadata.Name).VerifyConnection(); - } - - /// - public ITGAdministration Administration => UserIsAdministrator ? serverInterface.GetComponent(metadata.Name) : null; - - /// - public ITGByond Byond => serverInterface.GetComponent(metadata.Name); - - /// - public ITGChat Chat => serverInterface.GetComponent(metadata.Name); - - /// - public ITGCompiler Compiler => serverInterface.GetComponent(metadata.Name); - - /// - public ITGConfig Config => serverInterface.GetComponent(metadata.Name); - - /// - public ITGDreamDaemon DreamDaemon => serverInterface.GetComponent(metadata.Name); - - /// - public ITGInterop Interop => serverInterface.GetComponent(metadata.Name); - - /// - public ITGRepository Repository => serverInterface.GetComponent(metadata.Name); - - /// - public string ServerDirectory() - { - return serverInterface.GetComponent(metadata.Name).ServerDirectory(); - } - - /// - public string Version() - { - return serverInterface.GetComponent(metadata.Name).Version(); - } - - /// - /// Get a string representation of the - /// - /// A string representation of the - public override string ToString() - { - return String.Format("{0}: {1} - {2} - {3}", metadata.LoggingID, metadata.Name, metadata.Path, metadata.Enabled ? "ONLINE" : "OFFLINE"); - } - } -} diff --git a/legacy/V3/TGS.Interface/InstanceMetadata.cs b/legacy/V3/TGS.Interface/InstanceMetadata.cs deleted file mode 100644 index 8b3e73fa6e..0000000000 --- a/legacy/V3/TGS.Interface/InstanceMetadata.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Runtime.Serialization; - -namespace TGS.Interface -{ - /// - /// Metadata about an - /// - //Namespace required for compatibility reasons - [DataContract(Namespace = "http://schemas.datacontract.org/2004/07/TGServiceInterface")] - public sealed class InstanceMetadata - { - /// - /// The name of the - /// - [DataMember] - public string Name { get; set; } - /// - /// The path of the - /// - [DataMember] - public string Path { get; set; } - /// - /// Whether or not the is enabled - /// - [DataMember] - public bool Enabled { get; set; } - /// - /// The logging ID of the . Will be 0 if is - /// - [DataMember] - public byte LoggingID { get; set; } - } -} diff --git a/legacy/V3/TGS.Interface/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Interface/Properties/AssemblyInfo.cs deleted file mode 100644 index c9d6fe7641..0000000000 --- a/legacy/V3/TGS.Interface/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TGStation Server Service Interface")] -[assembly: AssemblyDescription("Used by user programs to access the TGStation Server Service")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab")] diff --git a/legacy/V3/TGS.Interface/PullRequestInfo.cs b/legacy/V3/TGS.Interface/PullRequestInfo.cs deleted file mode 100644 index e6de6188c1..0000000000 --- a/legacy/V3/TGS.Interface/PullRequestInfo.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Runtime.Serialization; - -namespace TGS.Interface -{ - /// - /// Information about a pull request - /// - [DataContract] - public sealed class PullRequestInfo - { - /// - /// The PR number - /// - [DataMember] - public int Number { get; private set; } - /// - /// The PR's author - /// - [DataMember] - public string Author { get; private set; } - /// - /// The PR's title - /// - [DataMember] - public string Title { get; private set; } - /// - /// The commit the PR was merged locally at - /// - [DataMember] - public string Sha { get; private set; } - - /// - /// Construct a - /// - /// The PR number - /// The PR's author - /// The PR's title - /// The commit the PR was merged locally at - public PullRequestInfo(int number, string author, string title, string sha) - { - Number = number; - Author = author; - Title = title; - Sha = sha; - } - - /// - /// Construct a for a call to - /// - /// The PR number - /// The optional commit to merge the PR at - public PullRequestInfo(int number, string sha = null) - { - Number = number; - Sha = sha; - } - } -} diff --git a/legacy/V3/TGS.Interface/RemoteLoginInfo.cs b/legacy/V3/TGS.Interface/RemoteLoginInfo.cs deleted file mode 100644 index 4fcc787de3..0000000000 --- a/legacy/V3/TGS.Interface/RemoteLoginInfo.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; - -namespace TGS.Interface -{ - /// - /// Information representing a remote server connection - /// - public sealed class RemoteLoginInfo : IEquatable - { - /// - /// Used for serialization - /// - const string EntropyFormatter = "{0}Entropy"; - - /// - /// The IP address or URL of the target server - /// - public string IP { get { return _ip; } } - /// - /// The port to connect to the target server - /// - public ushort Port { get { return _port; } } - /// - /// A Windows username for the target server - /// - public string Username { get { return _username; } } - /// - /// The Windows password for - /// - public string Password { internal get; set; } - /// - /// Check if the has been initialized with a - /// - [JsonIgnore] - public bool HasPassword { get { return !String.IsNullOrWhiteSpace(Password); } } - - /// - /// Backing field for - /// - readonly string _ip; - /// - /// Backing field for - /// - readonly ushort _port; - /// - /// Backing field for - /// - readonly string _username; - - /// - /// Construct a - /// - /// The value for - /// The value for - /// The value for - /// The value for - public RemoteLoginInfo(string ip, ushort port, string username, string password) - { - if (String.IsNullOrWhiteSpace(ip)) - throw new InvalidOperationException("ip must be set!"); - _ip = ip; - if (port == 0) - throw new InvalidOperationException("port may not be 0!"); - _port = port; - if (String.IsNullOrWhiteSpace(username)) - throw new InvalidOperationException("username must be set!"); - _username = username; - Password = password; - } - - /// - /// Construct a from JSON - /// - /// The result of a call to - public RemoteLoginInfo(string json) - { - var dic = JsonConvert.DeserializeObject>(json); - var ip = (string)dic[nameof(IP)]; - if (String.IsNullOrWhiteSpace(ip)) - throw new InvalidOperationException("ip must be set!"); - _ip = ip; - var port = (ushort)(long)dic[nameof(Port)]; - if (port == 0) - throw new InvalidOperationException("port may not be 0!"); - _port = port; - var username = (string)dic[nameof(Username)]; - if (String.IsNullOrWhiteSpace(username)) - throw new InvalidOperationException("username must be set!"); - _username = username; - if(dic.ContainsKey(nameof(Password))) - Password = Helpers.DecryptData((string)dic[nameof(Password)], (string)dic[String.Format(EntropyFormatter, nameof(Password))]); - } - - /// - /// Returns - /// - /// - public override string ToString() - { - return IP; - } - - /// - /// Checks if another matches one - /// - /// Another - /// if and have the same , , and - public bool Equals(RemoteLoginInfo other) - { - return other != null && IP == other.IP && Port == other.Port && Username == other.Username; - } - - /// - /// Returns a JSON representation of the with the encrypted - /// - /// A JSON representation of the - public string ToJSON() - { - //serialize it to a dic first so we can store the entropy - var raw = JsonConvert.SerializeObject(this); - var dic = JsonConvert.DeserializeObject>(raw); - if (HasPassword) - { - dic.Add(nameof(Password), Helpers.EncryptData(Password, out string entropy)); - dic.Add(String.Format(EntropyFormatter, nameof(Password)), entropy); - } - return JsonConvert.SerializeObject(dic); - } - } -} diff --git a/legacy/V3/TGS.Interface/RootCommand.cs b/legacy/V3/TGS.Interface/RootCommand.cs deleted file mode 100644 index 129c772478..0000000000 --- a/legacy/V3/TGS.Interface/RootCommand.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TGS.Interface -{ - /// - /// Helper for creating commands that contain sub commands - /// - public abstract class RootCommand : Command - { - /// - /// s further down the tree from this one. Set in Constructor - /// - public Command[] Children { get; protected set; } = { }; - /// - /// If set to a multiline, detailed list of s will be printed. Otherwise a singleline list of s will be printed - /// - public static bool PrintHelpList = false; - - /// - /// Forward parameters to commands further down the tree - /// - /// List of parameters passed to the - /// The result of a sub or an appropriate if the handled it - protected override ExitCode Run(IList parameters) - { - if (parameters.Count > 0) - { - var LocalKeyword = parameters[0].Trim().ToLower(); - parameters.RemoveAt(0); - switch (LocalKeyword) - { - case "help": - case "?": - PrintHelp(); - return ExitCode.Normal; - default: - foreach (var c in Children) - if (c.Keyword == LocalKeyword) - { - if (parameters.Count > 0) - { - var possibleHelp = parameters[0].ToLower(); - if (possibleHelp == "help" || possibleHelp == "?") - { - c.PrintHelp(); - return ExitCode.Normal; - } - } - if (parameters.Count < c.RequiredParameters) - { - OutputProc("Not enough parameters!"); - return ExitCode.BadCommand; - } - return c.DoRun(parameters); - } - parameters.Insert(0, LocalKeyword); - break; - } - } - OutputProc(String.Format("Invalid command! Type '{0}?' or '{0}help' for available commands.", Keyword != null ? Keyword + " " : "")); - return ExitCode.BadCommand; - } - - /// - public override void PrintHelp() - { - var Final = new List(); - if (PrintHelpList) - { - foreach (var c in Children) - Final.Add(c.Keyword); - OutputProc("Available commands (type '?' or 'help' after command for more info): " + String.Join(", ", Final)); - } - else - { - var Prefixes = new List(); - var Postfixes = new List(); - int MaxPrefixLen = 0; - foreach (var c in Children) - { - var ns = c.Keyword + " " + c.GetArgumentString(); - MaxPrefixLen = Math.Max(MaxPrefixLen, ns.Length); - Prefixes.Add(ns); - Postfixes.Add(c.GetHelpText()); - } - - for (var I = 0; I < Prefixes.Count; ++I) - { - var lp = Prefixes[I]; - for (; lp.Length < MaxPrefixLen + 1; lp += " ") ; - Final.Add(lp + "- " + Postfixes[I]); - } - Final.Sort(); - Final.ForEach(OutputProc); - } - } - - /// - public override string GetHelpText() - { - throw new NotImplementedException(); - } - } -} diff --git a/legacy/V3/TGS.Interface/Server.cs b/legacy/V3/TGS.Interface/Server.cs deleted file mode 100644 index d934528832..0000000000 --- a/legacy/V3/TGS.Interface/Server.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using TGS.Interface.Components; - -namespace TGS.Interface -{ - /// - sealed class Server : IServer - { - /// - public Version Version => serverInterface.ServerVersion; - - /// - public IEnumerable Instances { get - { - lock (this) - if (knownInstances == null) - knownInstances = serverInterface.GetComponent(null).ListInstances(); - foreach (var I in knownInstances) - { - IInstance nextInstance; - try - { - nextInstance = new Instance(serverInterface, I); - } - catch - { - continue; - } - yield return nextInstance; - } - } - } - - /// - public ITGInstanceManager InstanceManager => UserIsAdministrator ? serverInterface.GetComponent(null) : null; - - /// - public ITGSService Management => UserIsAdministrator ? serverInterface.GetComponent(null) : null; - - /// - /// If the connected user is an administrator of the - /// - bool UserIsAdministrator - { - get - { - lock (this) - { - if (isAdministrator) - return true; - try - { - serverInterface.GetComponent(null).Version(); - isAdministrator = true; - return true; - } - catch - { - return false; - } - } - } - } - - /// - /// The backing - /// - readonly Client serverInterface; - - /// - /// Result of a call to - /// - IList knownInstances; - - /// - /// Backing field for - /// - bool isAdministrator; - - /// - /// Construct an - /// - /// The to use - public Server(Client _serverInterface) - { - serverInterface = _serverInterface; - } - - /// - public IInstance GetInstance(string name) - { - return new Instance(serverInterface, new InstanceMetadata { Name = name, Enabled = false }); - } - } -} diff --git a/legacy/V3/TGS.Interface/TGS.Interface.csproj b/legacy/V3/TGS.Interface/TGS.Interface.csproj deleted file mode 100644 index 42cb6b71b0..0000000000 --- a/legacy/V3/TGS.Interface/TGS.Interface.csproj +++ /dev/null @@ -1,92 +0,0 @@ - - - - - Debug - AnyCPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB} - Library - Properties - TGS.Interface - TGServiceInterface - v4.5.2 - 512 - - - tgs.ico - - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\ - TRACE - bin\x86\Release\TGS.Interface.xml - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - false - - - false - - - - ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Interface/TGS.Interface.nuspec b/legacy/V3/TGS.Interface/TGS.Interface.nuspec deleted file mode 100644 index 80bbf50f80..0000000000 --- a/legacy/V3/TGS.Interface/TGS.Interface.nuspec +++ /dev/null @@ -1,16 +0,0 @@ - - - - TGServiceInterface - $version$ - Cyberboss - https://github.com/tgstation/tgstation-server/blob/master/LICENSE - https://github.com/tgstation/tgstation-server - https://tgstation.github.io/tgstation-server/tgs.ico - false - Interface for managing /tg/station server - Copyright 2017 - https://github.com/tgstation/tgstation-server/releases/tag/tgstation-server-v$version$ - tgstation game-server byond service toolset wcf ss13 client-lib tgstation-server api wcf-service soap - - diff --git a/legacy/V3/TGS.Interface/packages.config b/legacy/V3/TGS.Interface/packages.config deleted file mode 100644 index 810e559c04..0000000000 --- a/legacy/V3/TGS.Interface/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Interface/tgs.ico b/legacy/V3/TGS.Interface/tgs.ico deleted file mode 100644 index 6ed69fadfcdf8deb049f66dfffc46d5361332feb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123198 zcmeHQ2b>f|_HG1(QxxyiJH1mu&xnDbAQ>bqf<%dugM>v;q9P!>AR-bJ0TGd8IK_aX z2#5hz#f;%jFx}~`r~lK_Logl)QvdI(nVPNX?V0K7shQnbrhdPk?y0V>diCn-SFc{Z zs&<@mPDSUKV;r2TJ0mJMP7lX%PB zX4{+n+bBCg5VM}LHT8#F$viu1R(UX^)tZ+V_Gx?kJFQ0BSK&->jo zecN3mM)kc^4DH@rEW2%nSTJ*<7}B+=xUyR_F|boZaasN9&s zh;pEBOrtZae1USrH3Qp=F@rjaZ~pUd@%5MgC$2(XU7MdHx?WO4^lDQ}4DZoGG&=Lh zFD||4PhcZ@>PM_~5;_BpuAV zX^pCa&+yq^IW>AHEX@4(t~{e7{E={PkDy%f5YL+>p-V z=E)-kcp&&p&(<|hYH`8w%fY7uqx!a$&pfx`(a`hXe+Tl4!vB8$NsR2%Myy_P3+mTF z@EJY}IzDB+pIEa}CVuv*N1~qpcGuTp@hy|Z^;ca1`kRQ4Klqzi|Hx{#3G47_t6G1! zsotp-53=qI&&7SdsrcY;Z^=6F89v+Q!aokh_5S?vgFT;@k@%7H_iim7dtjNEdEGF< zXZURE+9%vhoDAsHKz#c555)5u!}a&z-`K&O#KM~=$@=jbKHI**sTVapr|MSLXKvm# zV$QUy#o)`E2-SY$(0=b|?Kh%VOVQ}8D&T+Bt-#Vnt!kg(bZTA6OsOvHqVc1Fuzh z{sHB#0?vkYYJA42mz;O3ELZ;l9-N#yCsqPKX5n4?>XP%1U5oP8qOM;7Z?n)|l@O;t z;uKbLLOVhJ*Zn|!G1+-^WT0Wa7P(}^v+^9;2hNn$Ko-4qU>EQ3(;Ne%~DG%`I zYs4+*J*Mf<@YHDoJ2$M-&n4g7W!*M&qEiig1AYcVZtMk?iD#K7+f4nhTRjx7{xdCP z*FZjd>x}X4Y~}xX)5kfLaW$}0gY$utop?te_ZkS)N1)9`>pCY&oZmWQoQxaLv4MPd z`n9ho+Fe|Y*xEUuWBv0FM;^>eg_U>Ac0T^_y>h+V);SeqD&JjGW9&nkMb^QqW?m=#R0PIhM>^pVG+pm|qYkr^7X0JG9XQ?-YgR6j@LeZ@SP_Z4kwIpARtbg^iE>Mf@GIRE3>?eG_yaE9TrVTJoxHwF;CmKcd|l zUD2ktYzDN?7+~Pni>j3uQ?KqTAN}vw#q(Er>Svih+MoU5v@!h!^I%@>Ep=EXdjH~P37$j0w4Mw{S$5U&($PDp=L{=`4< zdHYS*iq~Iy4tbm^`OiGNwKz8idR`0Q^9$9+khfCSke7bnFZP{$Z~8z-RPvU2GVd3k z+i)0k)SOToRHB|JmMxee=H-u}o*=G*?xf_y%Uhoo4Iyv5K?ikO)I8n+*6N~8pLT9?hL{YUitorpr<9lSLdmyJ|Ng$rgE9^Mi>G`;-}7nD zoSJnZ`_NA{mhu5I*4elBf93EV-i^Lm>Z3CzUMY3AJLXIkKmG8%WI1uO`H3}R`h+23 z9CRXGK2ygdO_yA7+}-WBS5Sfd~#}4j@qmx*% zXqLEf;!yN6^(EffztsP8C1QR*^Kk1DETivbb)6xXH#ru38wI|vY<}+VUPYVB_m1x) z56V{_MgJ?3=AXdtSHZ89$a~b_E{%_cE~UsWcE0D%xz6j`o-c>GABE?}W6bdbvBd*z z%ew7X{~Mfs#1Fv9_~AWU9<}MQzm!`zYf`cNVnqN)4#o}@(Kk&;-}(*ut98IxE_AS5 z=vwR0R^NaR)6t(*#P}yxHjbDsoV`vpdF*kjI{DC++(*oZwHpOad51@#Q%N4o^PrO_ z$9vV0t~ri~9)*FFB{pip4h{KIQ7Zai2B0 zIkI;fnH=N3Gogn!zj{FX+IY`S2A*m{p3Z~*`n76LwB>(+uZ4ZuU09Q8n3m7*S!Oc4 zXA6^}Wm@2$xUG)*y@B@Jhqm4YJ*E(4eFJPARAs9(Fm(|9`!`I(w0wro0=LyMMm8#c z(#W39a~szl2Hej>zJfBCvY+p9eICFxuc=S+;C1v7O3P=V56pXJ{lkY{JAzh(#NneI z?>NvUocnY|6wQP0oq$Uv}!TNtW(5;&|z-8cBs^+IIf0l z6yJZh8)L}3#ge%RFTlmi=qGaX#|=9E>i(BSSkb=WY0&Q*4u;%{!WH%9+$&niI@dnQk-GVT{re-` z?E4sdQ~xJz>m);mSK^xRY8{l9J()JwO&S|A`>#f2w_^nLanqdE zw87H(d6FNhe8{Vrs_e<6FUxHgjAoEyB$KXBlH9K-C}yH^}McrcQl z7fa{m$?~ro(L+4_#DjADp~`1jEOf%K%VqyKv|F>YATQoiyeEFpmqqf6RemIa$Hmfl z(<0@+ymb@D9+9?XSeKc@=lZL99RvKXQFZ4#NBbDZWGW<6K6UUtK89)cABkIZ1sJ;E-cz+I`e} z$TC@W$Hu4Mt$0saP8mbnpdJ#H&oK_i&unA7-+0H4kk`v^pNa9wW;_x3Gf0PZ( zQ`M1Wb!`0Sxh(tjm!7TIw(iMm!g~49KbSXcscnHP2lFPfZ1ppJ{168^U3t8h_mTh9 z88}|gy`r@oi&Fp08#^$neD=GX%b;9=&PF@DhyFEH&d-oW=Dp?Vb>*i|xKb4xb%xGE z8*X2`p9iQ*!Oke4tBLv3$4R}C??vG!A$tt0q5u2^_9u?Zsf+6R zCv|t^eSmqhj93A7DB2uPhiv>8vC4Mf!E4qH{kG)A;@Oi0=O$n~mNs5qvrNvJP#4$r zZJmAl93{^pak!8E?|b0!dg%K#t{Kq&xA!l*wfwxi#N|c^&@RAZr+dqDDlh zXn4kv`yoTVM4NmGT_R#zK>YWJ8x0#^1;}+Rr{I^cH*kDV0lNMT$os#L-J3Dq&BYvO zEzDolLRq<&HK@KBH2$|$?LXYmw|%_|u(A6pQ0%S7*%NWlaqhSRF%U<@PH}qjb_!${H(*ZLS$NZR6(C4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl - - - - - diff --git a/legacy/V3/TGS.Server.Console/Console.cs b/legacy/V3/TGS.Server.Console/Console.cs deleted file mode 100644 index 9641e5d625..0000000000 --- a/legacy/V3/TGS.Server.Console/Console.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; - -namespace TGS.Server.Console -{ - /// - /// Console runner for a - /// - sealed class Console : ILogger - { - /// - /// Entry point to the - /// - /// - static void Main(string[] args) => new Console(args); - - /// - /// Construct and run a - /// - /// Command line arguments - Console(string[] args) - { - try - { - System.Console.WriteLine("Starting server..."); - var server = new Server(args, this); //no using to avoid including more references - try - { - System.Console.WriteLine("Server started!"); - ExitPrompt(); - } - finally - { - server.Dispose(); - } - } - catch (Exception e) - { - System.Console.WriteLine(String.Format("Unhandled exception: {0}", e.ToString())); - ExitPrompt(); - } - } - - void ExitPrompt() - { - System.Console.WriteLine("Press any key to exit..."); - System.Console.ReadKey(); - } - - /// - public void WriteAccess(string username, bool authSuccess, byte loggingID) - { - System.Console.WriteLine(String.Format("[{0}]: {1}-{4}: Authentication {3} from {2}", DateTime.UtcNow.ToString(), EventID.Authentication, username, authSuccess ? "success" : "fail", loggingID)); - } - - /// - public void WriteError(string message, EventID id, byte loggingID) - { - System.Console.WriteLine(String.Format("[{0}]: {1}-{3}: ERROR: {2}", DateTime.Now.ToString(), id, message, loggingID)); - } - - /// - public void WriteInfo(string message, EventID id, byte loggingID) - { - System.Console.WriteLine(String.Format("[{0}]: {1}-{3}: {2}", DateTime.Now.ToString(), id, message, loggingID)); - } - - /// - public void WriteWarning(string message, EventID id, byte loggingID) - { - System.Console.WriteLine(String.Format("[{0}]: {1}-{3}: Warning: {2}", DateTime.Now.ToString(), id, message, loggingID)); - } - } -} diff --git a/legacy/V3/TGS.Server.Console/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Server.Console/Properties/AssemblyInfo.cs deleted file mode 100644 index 0ce360e6d0..0000000000 --- a/legacy/V3/TGS.Server.Console/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TGStation Server Console")] -[assembly: AssemblyDescription("Console adapter for TGStation Server")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("509433f6-aefb-44ca-bfe3-c782166d2cc3")] diff --git a/legacy/V3/TGS.Server.Console/TGS.Server.Console.csproj b/legacy/V3/TGS.Server.Console/TGS.Server.Console.csproj deleted file mode 100644 index 37d49bf3a8..0000000000 --- a/legacy/V3/TGS.Server.Console/TGS.Server.Console.csproj +++ /dev/null @@ -1,89 +0,0 @@ - - - - - Debug - AnyCPU - {509433F6-AEFB-44CA-BFE3-C782166D2CC3} - Exe - TGS.Server.Console - TGS.Server.Console - v4.5.2 - 512 - true - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - app.manifest - - - tgs.ico - - - - - - - - - - - - - - - {f32eda25-0855-411c-af5e-f0d042917e2d} - TGS.Server - - - - - False - Microsoft .NET Framework 4.6.1 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Server.Console/app.manifest b/legacy/V3/TGS.Server.Console/app.manifest deleted file mode 100644 index 467015914e..0000000000 --- a/legacy/V3/TGS.Server.Console/app.manifest +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/legacy/V3/TGS.Server.Console/tgs.ico b/legacy/V3/TGS.Server.Console/tgs.ico deleted file mode 100644 index 6ed69fadfcdf8deb049f66dfffc46d5361332feb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123198 zcmeHQ2b>f|_HG1(QxxyiJH1mu&xnDbAQ>bqf<%dugM>v;q9P!>AR-bJ0TGd8IK_aX z2#5hz#f;%jFx}~`r~lK_Logl)QvdI(nVPNX?V0K7shQnbrhdPk?y0V>diCn-SFc{Z zs&<@mPDSUKV;r2TJ0mJMP7lX%PB zX4{+n+bBCg5VM}LHT8#F$viu1R(UX^)tZ+V_Gx?kJFQ0BSK&->jo zecN3mM)kc^4DH@rEW2%nSTJ*<7}B+=xUyR_F|boZaasN9&s zh;pEBOrtZae1USrH3Qp=F@rjaZ~pUd@%5MgC$2(XU7MdHx?WO4^lDQ}4DZoGG&=Lh zFD||4PhcZ@>PM_~5;_BpuAV zX^pCa&+yq^IW>AHEX@4(t~{e7{E={PkDy%f5YL+>p-V z=E)-kcp&&p&(<|hYH`8w%fY7uqx!a$&pfx`(a`hXe+Tl4!vB8$NsR2%Myy_P3+mTF z@EJY}IzDB+pIEa}CVuv*N1~qpcGuTp@hy|Z^;ca1`kRQ4Klqzi|Hx{#3G47_t6G1! zsotp-53=qI&&7SdsrcY;Z^=6F89v+Q!aokh_5S?vgFT;@k@%7H_iim7dtjNEdEGF< zXZURE+9%vhoDAsHKz#c555)5u!}a&z-`K&O#KM~=$@=jbKHI**sTVapr|MSLXKvm# zV$QUy#o)`E2-SY$(0=b|?Kh%VOVQ}8D&T+Bt-#Vnt!kg(bZTA6OsOvHqVc1Fuzh z{sHB#0?vkYYJA42mz;O3ELZ;l9-N#yCsqPKX5n4?>XP%1U5oP8qOM;7Z?n)|l@O;t z;uKbLLOVhJ*Zn|!G1+-^WT0Wa7P(}^v+^9;2hNn$Ko-4qU>EQ3(;Ne%~DG%`I zYs4+*J*Mf<@YHDoJ2$M-&n4g7W!*M&qEiig1AYcVZtMk?iD#K7+f4nhTRjx7{xdCP z*FZjd>x}X4Y~}xX)5kfLaW$}0gY$utop?te_ZkS)N1)9`>pCY&oZmWQoQxaLv4MPd z`n9ho+Fe|Y*xEUuWBv0FM;^>eg_U>Ac0T^_y>h+V);SeqD&JjGW9&nkMb^QqW?m=#R0PIhM>^pVG+pm|qYkr^7X0JG9XQ?-YgR6j@LeZ@SP_Z4kwIpARtbg^iE>Mf@GIRE3>?eG_yaE9TrVTJoxHwF;CmKcd|l zUD2ktYzDN?7+~Pni>j3uQ?KqTAN}vw#q(Er>Svih+MoU5v@!h!^I%@>Ep=EXdjH~P37$j0w4Mw{S$5U&($PDp=L{=`4< zdHYS*iq~Iy4tbm^`OiGNwKz8idR`0Q^9$9+khfCSke7bnFZP{$Z~8z-RPvU2GVd3k z+i)0k)SOToRHB|JmMxee=H-u}o*=G*?xf_y%Uhoo4Iyv5K?ikO)I8n+*6N~8pLT9?hL{YUitorpr<9lSLdmyJ|Ng$rgE9^Mi>G`;-}7nD zoSJnZ`_NA{mhu5I*4elBf93EV-i^Lm>Z3CzUMY3AJLXIkKmG8%WI1uO`H3}R`h+23 z9CRXGK2ygdO_yA7+}-WBS5Sfd~#}4j@qmx*% zXqLEf;!yN6^(EffztsP8C1QR*^Kk1DETivbb)6xXH#ru38wI|vY<}+VUPYVB_m1x) z56V{_MgJ?3=AXdtSHZ89$a~b_E{%_cE~UsWcE0D%xz6j`o-c>GABE?}W6bdbvBd*z z%ew7X{~Mfs#1Fv9_~AWU9<}MQzm!`zYf`cNVnqN)4#o}@(Kk&;-}(*ut98IxE_AS5 z=vwR0R^NaR)6t(*#P}yxHjbDsoV`vpdF*kjI{DC++(*oZwHpOad51@#Q%N4o^PrO_ z$9vV0t~ri~9)*FFB{pip4h{KIQ7Zai2B0 zIkI;fnH=N3Gogn!zj{FX+IY`S2A*m{p3Z~*`n76LwB>(+uZ4ZuU09Q8n3m7*S!Oc4 zXA6^}Wm@2$xUG)*y@B@Jhqm4YJ*E(4eFJPARAs9(Fm(|9`!`I(w0wro0=LyMMm8#c z(#W39a~szl2Hej>zJfBCvY+p9eICFxuc=S+;C1v7O3P=V56pXJ{lkY{JAzh(#NneI z?>NvUocnY|6wQP0oq$Uv}!TNtW(5;&|z-8cBs^+IIf0l z6yJZh8)L}3#ge%RFTlmi=qGaX#|=9E>i(BSSkb=WY0&Q*4u;%{!WH%9+$&niI@dnQk-GVT{re-` z?E4sdQ~xJz>m);mSK^xRY8{l9J()JwO&S|A`>#f2w_^nLanqdE zw87H(d6FNhe8{Vrs_e<6FUxHgjAoEyB$KXBlH9K-C}yH^}McrcQl z7fa{m$?~ro(L+4_#DjADp~`1jEOf%K%VqyKv|F>YATQoiyeEFpmqqf6RemIa$Hmfl z(<0@+ymb@D9+9?XSeKc@=lZL99RvKXQFZ4#NBbDZWGW<6K6UUtK89)cABkIZ1sJ;E-cz+I`e} z$TC@W$Hu4Mt$0saP8mbnpdJ#H&oK_i&unA7-+0H4kk`v^pNa9wW;_x3Gf0PZ( zQ`M1Wb!`0Sxh(tjm!7TIw(iMm!g~49KbSXcscnHP2lFPfZ1ppJ{168^U3t8h_mTh9 z88}|gy`r@oi&Fp08#^$neD=GX%b;9=&PF@DhyFEH&d-oW=Dp?Vb>*i|xKb4xb%xGE z8*X2`p9iQ*!Oke4tBLv3$4R}C??vG!A$tt0q5u2^_9u?Zsf+6R zCv|t^eSmqhj93A7DB2uPhiv>8vC4Mf!E4qH{kG)A;@Oi0=O$n~mNs5qvrNvJP#4$r zZJmAl93{^pak!8E?|b0!dg%K#t{Kq&xA!l*wfwxi#N|c^&@RAZr+dqDDlh zXn4kv`yoTVM4NmGT_R#zK>YWJ8x0#^1;}+Rr{I^cH*kDV0lNMT$os#L-J3Dq&BYvO zEzDolLRq<&HK@KBH2$|$?LXYmw|%_|u(A6pQ0%S7*%NWlaqhSRF%U<@PH}qjb_!${H(*ZLS$NZR6(C4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl - - - - - diff --git a/legacy/V3/TGS.Server.Service/ProjectInstaller.cs b/legacy/V3/TGS.Server.Service/ProjectInstaller.cs deleted file mode 100644 index 10c319e3d0..0000000000 --- a/legacy/V3/TGS.Server.Service/ProjectInstaller.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.ComponentModel; -using System.Configuration.Install; -using System.ServiceProcess; - -namespace TGS.Server.Service -{ - /// - /// This tells the .msi there is a Windows in this that needs installation - /// - [RunInstaller(true)] - public sealed class ProjectInstaller : Installer - { - /// - /// Construct a - /// - public ProjectInstaller() - { - Installers.AddRange(new Installer[] { - new ServiceProcessInstaller - { - Account = ServiceAccount.LocalSystem, - Password = null, - Username = null - }, - new ServiceInstaller - { - Description = "/tg/station Server Service", - DisplayName = "TG Station Server", - ServiceName = "TG Station Server", - StartType = ServiceStartMode.Automatic - } - }); - } - } -} diff --git a/legacy/V3/TGS.Server.Service/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Server.Service/Properties/AssemblyInfo.cs deleted file mode 100644 index e4e22f2673..0000000000 --- a/legacy/V3/TGS.Server.Service/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TGStation Server Service")] -[assembly: AssemblyDescription("Windows service adapter for TGStation Server")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("3f81e398-b223-4006-b40c-c2800714ce29")] diff --git a/legacy/V3/TGS.Server.Service/Service.cs b/legacy/V3/TGS.Server.Service/Service.cs deleted file mode 100644 index 2314595060..0000000000 --- a/legacy/V3/TGS.Server.Service/Service.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Diagnostics; -using System.ServiceProcess; - -namespace TGS.Server.Service -{ - /// - /// Windows adapter for - /// - public sealed class Service : ServiceBase, ILogger - { - /// - /// The entry point for the program. Calls with a new as a parameter - /// - public static void Main() => Run(new Service()); - - /// - /// The the manages - /// - Server activeServer; - - /// - /// Constructs a - /// - Service() - { - ServiceName = "TG Station Server"; - } - - /// - public void WriteAccess(string username, bool authSuccess, byte loggingID) - { - EventLog.WriteEntry(String.Format("Access from: {0}", username), authSuccess ? EventLogEntryType.SuccessAudit : EventLogEntryType.FailureAudit , (int)EventID.Authentication + loggingID); - } - - /// - public void WriteError(string message, EventID id, byte loggingID) - { - EventLog.WriteEntry(message, EventLogEntryType.Error, (int)id + loggingID); - } - - /// - public void WriteInfo(string message, EventID id, byte loggingID) - { - EventLog.WriteEntry(message, EventLogEntryType.Information, (int)id + loggingID); - } - - /// - public void WriteWarning(string message, EventID id, byte loggingID) - { - EventLog.WriteEntry(message, EventLogEntryType.Warning, (int)id + loggingID); - } - - /// - /// Called when the is started. Creates a new - /// - /// The service start arguments - protected override void OnStart(string[] args) - { - activeServer = new Server(args, this); - } - - /// - /// Called when the is stopped. Calls on - /// - protected override void OnStop() - { - activeServer.Dispose(); - } - } -} diff --git a/legacy/V3/TGS.Server.Service/TGS.Server.Service.csproj b/legacy/V3/TGS.Server.Service/TGS.Server.Service.csproj deleted file mode 100644 index 79fb1a5a22..0000000000 --- a/legacy/V3/TGS.Server.Service/TGS.Server.Service.csproj +++ /dev/null @@ -1,69 +0,0 @@ - - - - - Debug - AnyCPU - {3F81E398-B223-4006-B40C-C2800714CE29} - WinExe - TGS.Server.Service - TGServerService - v4.5.2 - 512 - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - TGS.Server.Service.Service - - - tgs.ico - - - - - - - - - Component - - - Component - - - - - - - - - - {f32eda25-0855-411c-af5e-f0d042917e2d} - TGS.Server - - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Server.Service/tgs.ico b/legacy/V3/TGS.Server.Service/tgs.ico deleted file mode 100644 index 6ed69fadfcdf8deb049f66dfffc46d5361332feb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123198 zcmeHQ2b>f|_HG1(QxxyiJH1mu&xnDbAQ>bqf<%dugM>v;q9P!>AR-bJ0TGd8IK_aX z2#5hz#f;%jFx}~`r~lK_Logl)QvdI(nVPNX?V0K7shQnbrhdPk?y0V>diCn-SFc{Z zs&<@mPDSUKV;r2TJ0mJMP7lX%PB zX4{+n+bBCg5VM}LHT8#F$viu1R(UX^)tZ+V_Gx?kJFQ0BSK&->jo zecN3mM)kc^4DH@rEW2%nSTJ*<7}B+=xUyR_F|boZaasN9&s zh;pEBOrtZae1USrH3Qp=F@rjaZ~pUd@%5MgC$2(XU7MdHx?WO4^lDQ}4DZoGG&=Lh zFD||4PhcZ@>PM_~5;_BpuAV zX^pCa&+yq^IW>AHEX@4(t~{e7{E={PkDy%f5YL+>p-V z=E)-kcp&&p&(<|hYH`8w%fY7uqx!a$&pfx`(a`hXe+Tl4!vB8$NsR2%Myy_P3+mTF z@EJY}IzDB+pIEa}CVuv*N1~qpcGuTp@hy|Z^;ca1`kRQ4Klqzi|Hx{#3G47_t6G1! zsotp-53=qI&&7SdsrcY;Z^=6F89v+Q!aokh_5S?vgFT;@k@%7H_iim7dtjNEdEGF< zXZURE+9%vhoDAsHKz#c555)5u!}a&z-`K&O#KM~=$@=jbKHI**sTVapr|MSLXKvm# zV$QUy#o)`E2-SY$(0=b|?Kh%VOVQ}8D&T+Bt-#Vnt!kg(bZTA6OsOvHqVc1Fuzh z{sHB#0?vkYYJA42mz;O3ELZ;l9-N#yCsqPKX5n4?>XP%1U5oP8qOM;7Z?n)|l@O;t z;uKbLLOVhJ*Zn|!G1+-^WT0Wa7P(}^v+^9;2hNn$Ko-4qU>EQ3(;Ne%~DG%`I zYs4+*J*Mf<@YHDoJ2$M-&n4g7W!*M&qEiig1AYcVZtMk?iD#K7+f4nhTRjx7{xdCP z*FZjd>x}X4Y~}xX)5kfLaW$}0gY$utop?te_ZkS)N1)9`>pCY&oZmWQoQxaLv4MPd z`n9ho+Fe|Y*xEUuWBv0FM;^>eg_U>Ac0T^_y>h+V);SeqD&JjGW9&nkMb^QqW?m=#R0PIhM>^pVG+pm|qYkr^7X0JG9XQ?-YgR6j@LeZ@SP_Z4kwIpARtbg^iE>Mf@GIRE3>?eG_yaE9TrVTJoxHwF;CmKcd|l zUD2ktYzDN?7+~Pni>j3uQ?KqTAN}vw#q(Er>Svih+MoU5v@!h!^I%@>Ep=EXdjH~P37$j0w4Mw{S$5U&($PDp=L{=`4< zdHYS*iq~Iy4tbm^`OiGNwKz8idR`0Q^9$9+khfCSke7bnFZP{$Z~8z-RPvU2GVd3k z+i)0k)SOToRHB|JmMxee=H-u}o*=G*?xf_y%Uhoo4Iyv5K?ikO)I8n+*6N~8pLT9?hL{YUitorpr<9lSLdmyJ|Ng$rgE9^Mi>G`;-}7nD zoSJnZ`_NA{mhu5I*4elBf93EV-i^Lm>Z3CzUMY3AJLXIkKmG8%WI1uO`H3}R`h+23 z9CRXGK2ygdO_yA7+}-WBS5Sfd~#}4j@qmx*% zXqLEf;!yN6^(EffztsP8C1QR*^Kk1DETivbb)6xXH#ru38wI|vY<}+VUPYVB_m1x) z56V{_MgJ?3=AXdtSHZ89$a~b_E{%_cE~UsWcE0D%xz6j`o-c>GABE?}W6bdbvBd*z z%ew7X{~Mfs#1Fv9_~AWU9<}MQzm!`zYf`cNVnqN)4#o}@(Kk&;-}(*ut98IxE_AS5 z=vwR0R^NaR)6t(*#P}yxHjbDsoV`vpdF*kjI{DC++(*oZwHpOad51@#Q%N4o^PrO_ z$9vV0t~ri~9)*FFB{pip4h{KIQ7Zai2B0 zIkI;fnH=N3Gogn!zj{FX+IY`S2A*m{p3Z~*`n76LwB>(+uZ4ZuU09Q8n3m7*S!Oc4 zXA6^}Wm@2$xUG)*y@B@Jhqm4YJ*E(4eFJPARAs9(Fm(|9`!`I(w0wro0=LyMMm8#c z(#W39a~szl2Hej>zJfBCvY+p9eICFxuc=S+;C1v7O3P=V56pXJ{lkY{JAzh(#NneI z?>NvUocnY|6wQP0oq$Uv}!TNtW(5;&|z-8cBs^+IIf0l z6yJZh8)L}3#ge%RFTlmi=qGaX#|=9E>i(BSSkb=WY0&Q*4u;%{!WH%9+$&niI@dnQk-GVT{re-` z?E4sdQ~xJz>m);mSK^xRY8{l9J()JwO&S|A`>#f2w_^nLanqdE zw87H(d6FNhe8{Vrs_e<6FUxHgjAoEyB$KXBlH9K-C}yH^}McrcQl z7fa{m$?~ro(L+4_#DjADp~`1jEOf%K%VqyKv|F>YATQoiyeEFpmqqf6RemIa$Hmfl z(<0@+ymb@D9+9?XSeKc@=lZL99RvKXQFZ4#NBbDZWGW<6K6UUtK89)cABkIZ1sJ;E-cz+I`e} z$TC@W$Hu4Mt$0saP8mbnpdJ#H&oK_i&unA7-+0H4kk`v^pNa9wW;_x3Gf0PZ( zQ`M1Wb!`0Sxh(tjm!7TIw(iMm!g~49KbSXcscnHP2lFPfZ1ppJ{168^U3t8h_mTh9 z88}|gy`r@oi&Fp08#^$neD=GX%b;9=&PF@DhyFEH&d-oW=Dp?Vb>*i|xKb4xb%xGE z8*X2`p9iQ*!Oke4tBLv3$4R}C??vG!A$tt0q5u2^_9u?Zsf+6R zCv|t^eSmqhj93A7DB2uPhiv>8vC4Mf!E4qH{kG)A;@Oi0=O$n~mNs5qvrNvJP#4$r zZJmAl93{^pak!8E?|b0!dg%K#t{Kq&xA!l*wfwxi#N|c^&@RAZr+dqDDlh zXn4kv`yoTVM4NmGT_R#zK>YWJ8x0#^1;}+Rr{I^cH*kDV0lNMT$os#L-J3Dq&BYvO zEzDolLRq<&HK@KBH2$|$?LXYmw|%_|u(A6pQ0%S7*%NWlaqhSRF%U<@PH}qjb_!${H(*ZLS$NZR6(C4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl - - - - - - - diff --git a/legacy/V3/TGS.Server/ChatCommands/ByondCommand.cs b/legacy/V3/TGS.Server/ChatCommands/ByondCommand.cs deleted file mode 100644 index b983339816..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/ByondCommand.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections.Generic; -using TGS.Interface; - -namespace TGS.Server.ChatCommands -{ - /// - /// Retrieve the installed, staged, or latest availab - /// - sealed class ByondCommand : ChatCommand - { - /// - /// Construct a - /// - public ByondCommand() - { - Keyword = "byond"; - } - - /// - protected override ExitCode Run(IList parameters) - { - var type = ByondVersion.Installed; - if (parameters.Count > 0) - if (parameters[0].ToLower() == "--staged") - type = ByondVersion.Staged; - else if (parameters[0].ToLower() == "--latest") - type = ByondVersion.Latest; - OutputProc(Instance.GetVersion(type) ?? "None"); - return ExitCode.Normal; - } - - /// - public override string GetHelpText() - { - return "Gets the specified BYOND version"; - } - - /// - public override string GetArgumentString() - { - return "[--staged|--latest]"; - } - } -} diff --git a/legacy/V3/TGS.Server/ChatCommands/ChatCommand.cs b/legacy/V3/TGS.Server/ChatCommands/ChatCommand.cs deleted file mode 100644 index 96d5803ca1..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/ChatCommand.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using TGS.Interface; - -namespace TGS.Server.ChatCommands -{ - /// - /// A command heard by a - /// - abstract class ChatCommand : Command - { - /// - /// for the - /// - public static ThreadLocal CommandInfo { get; private set; } = new ThreadLocal(); - /// - /// If set to , the cannot be invoked by a non-admin or outside an admin chat channel - /// - public bool RequiresAdmin { get; protected set; } - /// - /// Shorthand for accessing - /// - protected Instance Instance { get { return CommandInfo.Value.Server; } } - - /// - public override ExitCode DoRun(IList parameters) - { - if (RequiresAdmin) - { - var Info = CommandInfo.Value; - if (!Info.IsAdmin) - { - OutputProc("You are not authorized to use that command!"); - return ExitCode.BadCommand; - } - if (!Info.IsAdminChannel) - { - OutputProc("Use this command in an admin channel!"); - return ExitCode.BadCommand; - } - } - return base.DoRun(parameters); - } - } -} diff --git a/legacy/V3/TGS.Server/ChatCommands/CommandInfo.cs b/legacy/V3/TGS.Server/ChatCommands/CommandInfo.cs deleted file mode 100644 index 4108fd051f..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/CommandInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace TGS.Server.ChatCommands -{ - /// - /// Metadata about the currently running - /// - sealed class CommandInfo - { - /// - /// If the was invoked by an admin - /// - public bool IsAdmin { get; set; } - /// - /// If the was invoked from an admin chat channel - /// - public bool IsAdminChannel { get; set; } - /// - /// The name of the invoker - /// - public string Speaker { get; set; } - /// - /// A reference to the that runs the that heard the - /// - public Instance Server { get; set; } - } -} diff --git a/legacy/V3/TGS.Server/ChatCommands/KekCommand.cs b/legacy/V3/TGS.Server/ChatCommands/KekCommand.cs deleted file mode 100644 index 1826bf1876..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/KekCommand.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; - -namespace TGS.Server.ChatCommands -{ - /// - /// kek - /// - sealed class KekCommand : ChatCommand - { - /// - /// Construct a - /// - public KekCommand() - { - Keyword = "kek"; - } - - /// - protected override ExitCode Run(IList parameters) - { - OutputProc("kek"); - return ExitCode.Normal; - } - - /// - public override string GetHelpText() - { - return "kek"; - } - } -} diff --git a/legacy/V3/TGS.Server/ChatCommands/PullRequestsCommand.cs b/legacy/V3/TGS.Server/ChatCommands/PullRequestsCommand.cs deleted file mode 100644 index e03d426c04..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/PullRequestsCommand.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; - -namespace TGS.Server.ChatCommands -{ - /// - /// Retrieve the list of test-merged github pull requests - /// - sealed class PullRequestsCommand : ChatCommand - { - /// - /// Construct a - /// - public PullRequestsCommand() - { - Keyword = "prs"; - } - - /// - protected override ExitCode Run(IList parameters) - { - var PRs = Instance.MergedPullRequests(out string res); - if (PRs == null) - { - OutputProc(res); - return ExitCode.ServerError; - } - if (PRs.Count == 0) - OutputProc("None!"); - else - { - res = ""; - foreach (var I in PRs) - res += "#" + I.Number + " "; - OutputProc(res); - } - return ExitCode.Normal; - } - - /// - public override string GetHelpText() - { - return "Gets the currently merged pull requests in the repository"; - } - } -} diff --git a/legacy/V3/TGS.Server/ChatCommands/RevisionCommand.cs b/legacy/V3/TGS.Server/ChatCommands/RevisionCommand.cs deleted file mode 100644 index 7fd99c0f28..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/RevisionCommand.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TGS.Server.ChatCommands -{ - /// - /// Retrieves the git SHA of the live DreamDaemon code - /// - sealed class RevisionCommand : ChatCommand - { - /// - /// Construct a - /// - public RevisionCommand() - { - Keyword = "revision"; - } - /// - protected override ExitCode Run(IList parameters) - { - var res = Instance.LiveSha(); - if (res == "UNKNOWN") - { - OutputProc(res); - return ExitCode.ServerError; - } - OutputProc(String.Format("^{0}", res)); - return ExitCode.Normal; - } - - /// - public override string GetHelpText() - { - return "Prints the current code revision of the repository (not the server)"; - } - } -} diff --git a/legacy/V3/TGS.Server/ChatCommands/RootChatCommand.cs b/legacy/V3/TGS.Server/ChatCommands/RootChatCommand.cs deleted file mode 100644 index 800147a412..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/RootChatCommand.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using TGS.Interface; - -namespace TGS.Server.ChatCommands -{ - /// - /// The main root chat command - /// - sealed class RootChatCommand : RootCommand - { - /// - /// Construct a - /// - /// List of s supplied by DreamDaemon - public RootChatCommand(List serverCommands) - { - var tmp = new List { new PullRequestsCommand(), new VersionCommand(), new RevisionCommand(), new ByondCommand(), new KekCommand() }; - if (serverCommands != null) - tmp.AddRange(serverCommands); - Children = tmp.ToArray(); - serverCommands = new List(); - PrintHelpList = true; - } - } -} diff --git a/legacy/V3/TGS.Server/ChatCommands/ServerChatCommand.cs b/legacy/V3/TGS.Server/ChatCommands/ServerChatCommand.cs deleted file mode 100644 index fee9f70eb5..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/ServerChatCommand.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TGS.Server.ChatCommands -{ - /// - /// s generated by DreamDaemon via the API - /// - sealed class ServerChatCommand : ChatCommand - { - /// - /// The help text for the - /// - readonly string HelpText; - - /// - /// Construct a - /// - /// The invocation of the - /// The help text of the - /// If set to , the cannot be invoked by a non-admin or outside an admin chat channel - /// The number of parameters the requires - public ServerChatCommand(string name, string helpText, bool adminOnly, int requiredParameters) - { - Keyword = name; - RequiresAdmin = adminOnly; - HelpText = helpText; - RequiredParameters = requiredParameters; - } - - /// - public override string GetHelpText() - { - return HelpText; - } - - /// - protected override ExitCode Run(IList parameters) - { - var res = Instance.SendCommand(String.Format("{0};sender={1};custom={2}", Keyword, CommandInfo.Value.Speaker, Helpers.SanitizeTopicString(String.Join(" ", parameters)))); - if (res != "SUCCESS" && !String.IsNullOrWhiteSpace(res)) - OutputProc(res); - return ExitCode.Normal; - } - } -} diff --git a/legacy/V3/TGS.Server/ChatCommands/VersionCommand.cs b/legacy/V3/TGS.Server/ChatCommands/VersionCommand.cs deleted file mode 100644 index 538bcb4e7f..0000000000 --- a/legacy/V3/TGS.Server/ChatCommands/VersionCommand.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; - -namespace TGS.Server.ChatCommands -{ - /// - /// Retrieve the current service version - /// - sealed class VersionCommand : ChatCommand - { - /// - /// Construct a - /// - public VersionCommand() - { - Keyword = "version"; - } - - /// - protected override ExitCode Run(IList parameters) - { - OutputProc(Instance.Version()); - return ExitCode.Normal; - } - - /// - public override string GetHelpText() - { - return "Gets the running service version"; - } - } -} diff --git a/legacy/V3/TGS.Server/ChatProviders/ChatProvider.cs b/legacy/V3/TGS.Server/ChatProviders/ChatProvider.cs deleted file mode 100644 index 974d79eaf8..0000000000 --- a/legacy/V3/TGS.Server/ChatProviders/ChatProvider.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using TGS.Interface; - -namespace TGS.Server.ChatProviders -{ - /// - /// Callback for the chat provider recieving a - /// - /// The chat provider the message came from - /// The username of the speaker - /// The name of the channel - /// The message text - /// if is considered a chat admin, otherwise - /// if is an admin chat channel, otherwise - - delegate void OnChatMessage(IChatProvider ChatProvider, string speaker, string channel, string message, bool isAdmin, bool isAdminChannel); - /// - /// Interface for a chat provder service - /// - interface IChatProvider : IDisposable - { - /// - /// Sets for the provider - /// - /// The to set - /// null on success, error message on failure - string SetProviderInfo(ChatSetupInfo info); - - /// - /// Gets the info of the provider - /// - /// The for the chat provider - ChatSetupInfo ProviderInfo(); - - /// - /// Called with chat message info - /// - event OnChatMessage OnChatMessage; - - /// - /// Connects the chat provider if it's enabled - /// - /// on success, error message on failure - string Connect(); - /// - /// Forces a reconnection of the chat provider if it's enabled - /// - /// on success, error message on failure - string Reconnect(); - - /// - /// Checks if the chat provider is connected - /// - /// if the provider is connected, otherwise - bool Connected(); - - /// - /// Disconnects the chat provider - /// - void Disconnect(); - - /// - /// Send a to a - /// - /// The message to send - /// The channel to send to - /// on success, error message on failure - string SendMessageDirect(string message, string channel); - - /// - /// Broadcast a to appropriate channels based on the message type - /// - /// The message to send - /// The - void SendMessage(string message, MessageType mt); - } -} diff --git a/legacy/V3/TGS.Server/ChatProviders/DiscordChatProvider.cs b/legacy/V3/TGS.Server/ChatProviders/DiscordChatProvider.cs deleted file mode 100644 index 91326ba4bc..0000000000 --- a/legacy/V3/TGS.Server/ChatProviders/DiscordChatProvider.cs +++ /dev/null @@ -1,328 +0,0 @@ -using Discord; -using Discord.WebSocket; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using TGS.Interface; - -namespace TGS.Server.ChatProviders -{ - /// - /// for Discord: https://discordapp.com/ - /// - sealed class DiscordChatProvider : IChatProvider - { - /// - public event OnChatMessage OnChatMessage; - /// - /// The Discord API client - /// - DiscordSocketClient client; - /// - /// The setup info for the provider - /// - DiscordSetupInfo DiscordConfig; - /// - /// Used for multithreading safety - /// - object DiscordLock = new object(); - - /// - /// An of internal identifers => s we have seen - /// - IDictionary SeenPrivateChannels = new Dictionary(); - - /// - /// Construct a - /// - /// The - public DiscordChatProvider(ChatSetupInfo info) - { - Init(info); - } - - /// - public ChatSetupInfo ProviderInfo() - { - return DiscordConfig; - } - - /// - /// Sets up the Discord API and - /// - /// The to init with - void Init(ChatSetupInfo info) - { - DiscordConfig = new DiscordSetupInfo(info); - client = new DiscordSocketClient(); - client.MessageReceived += Client_MessageReceived; - } - - /// - /// Checks if a is considered a chat admin - /// - /// The sender of a message - /// if is a chat admin, otherwise - private bool CheckAdmin(SocketUser user) - { - if (!DiscordConfig.AdminsAreSpecial) - return DiscordConfig.AdminList.Contains(user.Id.ToString()); - if(user is SocketGuildUser sgu) - foreach (var I in sgu.Roles) - if (DiscordConfig.AdminList.Contains(I.Id.ToString())) - return true; - return false; - } - - /// - /// Called when a channel the bot is in recieves a message or the bot is PM'd directly - /// - /// The event arguments - /// The task to run when this occurs - private async Task Client_MessageReceived(SocketMessage e) - { - await Task.Run(() => - { - if (e.Author.Id == client.CurrentUser.Id) - return; - - var pm = e.Channel is IPrivateChannel; - - if (pm && !SeenPrivateChannels.ContainsKey(e.Channel.Id)) - SeenPrivateChannels.Add(e.Channel.Id, e.Channel); - - bool found = false; - - var formattedMessage = e.Content; - foreach (var u in e.MentionedUsers) - if (u.Id == client.CurrentUser.Id) - { - found = true; - formattedMessage = formattedMessage.Replace(u.Mention, ""); - var nicknameMention = u.Mention.Replace("!", ""); - formattedMessage = formattedMessage.Replace(nicknameMention, ""); - break; - } - - if (!found) - { - var splits = formattedMessage.Split(' '); - found = splits[0].ToLower() == "!tgs"; - if (found) - { - var asList = new List(); - asList.AddRange(splits); - asList.RemoveAt(0); - formattedMessage = String.Join(" ", asList); - } - } - - if (!found && !pm) - return; - - formattedMessage = formattedMessage.Trim(); - - var cid = e.Channel.Id.ToString(); - - OnChatMessage(this, e.Author.Id.ToString(), cid, formattedMessage, CheckAdmin(e.Author), pm || DiscordConfig.AdminChannels.Contains(cid)); - }); - } - - /// - public string Connect() - { - try - { - if (Connected() || !DiscordConfig.Enabled) - return null; - lock (DiscordLock) - { - SeenPrivateChannels.Clear(); - client.LoginAsync(TokenType.Bot, DiscordConfig.BotToken).Wait(); - client.StartAsync().Wait(); - Thread.Sleep(1000); //wait for ConnectionState to update - } - return !Connected() ? "Connection failed!" : null; - } - catch (Exception e) - { - return e.ToString(); - } - } - - /// - public bool Connected() - { - lock (DiscordLock) - { - return client.ConnectionState == ConnectionState.Connected; - } - } - - /// - public void Disconnect() - { - try - { - if (!Connected()) - return; - lock (DiscordLock) - { - SeenPrivateChannels.Clear(); - client.StopAsync().Wait(); - client.LogoutAsync().Wait(); - } - } - catch { } - } - - /// - public string Reconnect() - { - Disconnect(); - return Connect(); - } - - /// - public void SendMessage(string msg, MessageType mt) - { - if (!Connected()) - return; - lock (DiscordLock) - { - var tasks = new List(); - foreach (var I in client.Guilds) - foreach (var J in I.TextChannels) - { - var cid = J.Id.ToString(); - var wdc = DiscordConfig.WatchdogChannels; - bool SendToThisChannel = (mt.HasFlag(MessageType.AdminInfo) && DiscordConfig.AdminChannels.Contains(cid)) - || (mt.HasFlag(MessageType.DeveloperInfo) && DiscordConfig.DevChannels.Contains(cid)) - || (mt.HasFlag(MessageType.GameInfo) && DiscordConfig.GameChannels.Contains(cid)) - || (mt.HasFlag(MessageType.WatchdogInfo) && DiscordConfig.WatchdogChannels.Contains(cid)); - - if (SendToThisChannel) - tasks.Add(J.SendMessageAsync(msg)); - } - foreach (var I in tasks) - I.Wait(); - } - } - - /// - public string SendMessageDirect(string message, string channelname) - { - if (!Connected()) - return "Disconnected."; - try - { - lock (DiscordLock) - { - var tasks = new List(); - var channel = Convert.ToUInt64(channelname); - if (SeenPrivateChannels.ContainsKey(channel)) - SeenPrivateChannels[channel].SendMessageAsync(message).Wait(); - else - foreach (var I in client.Guilds) - foreach (var J in I.TextChannels) - if (J.Id == channel) - J.SendMessageAsync(message).Wait(); - return null; - } - } - catch (Exception e) - { - return e.ToString(); - } - } - - /// - /// Shutsdown and disposes - /// - void DisconnectAndDispose() - { - try - { - client.StopAsync().Wait(); - client.LogoutAsync().Wait(); - } - catch { } - client.Dispose(); - } - - /// - public string SetProviderInfo(ChatSetupInfo info) - { - try - { - lock (DiscordLock) - { - var odc = DiscordConfig; - DiscordConfig = new DiscordSetupInfo(info); - if (DiscordConfig.BotToken != odc.BotToken) - { - DisconnectAndDispose(); - Init(info); - } - if (DiscordConfig.Enabled) - { - if (!Connected()) - return Reconnect(); - } - else - Disconnect(); - } - return null; - } - catch (Exception e) - { - return e.ToString(); - } - } - - #region IDisposable Support - /// - /// To detect redundant calls - /// - private bool disposedValue = false; - - /// - /// Implements the pattern. Calls - /// - /// if was called manually, if it was from the finalizer - void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - DisconnectAndDispose(); - // TODO: dispose managed state (managed objects). - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~TGDiscordChatProvider() { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - /// - /// Implements the pattern - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - } -} diff --git a/legacy/V3/TGS.Server/ChatProviders/IRCChatProvider.cs b/legacy/V3/TGS.Server/ChatProviders/IRCChatProvider.cs deleted file mode 100644 index df453f4f11..0000000000 --- a/legacy/V3/TGS.Server/ChatProviders/IRCChatProvider.cs +++ /dev/null @@ -1,377 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using TGS.Interface; -using Meebey.SmartIrc4net; - -namespace TGS.Server.ChatProviders -{ - /// - /// for internet relay chat - /// - sealed class IRCChatProvider : IChatProvider - { - /// - /// Header used to mark that a channel is actually a query message - /// - const string PrivateMessageMarker = "---PRIVATE-MSG---"; - /// - /// The irc client - /// - IrcFeatures irc; - /// - /// Used for multithreading safety - /// - object IRCLock = new object(); - - /// - /// The setup info for the provider - /// - IRCSetupInfo IRCConfig; - - /// - public event OnChatMessage OnChatMessage; - - /// - public ChatSetupInfo ProviderInfo() - { - return IRCConfig; - } - - /// - /// Construct a - /// - /// The - public IRCChatProvider(ChatSetupInfo info) - { - IRCConfig = new IRCSetupInfo(info); - irc = new IrcFeatures() - { - SupportNonRfc = true, - CtcpUserInfo = Server.VersionString, - AutoRejoin = true, - AutoRejoinOnKick = true, - AutoRelogin = true, - AutoRetry = true, - AutoRetryLimit = 5, - AutoRetryDelay = 5, - ActiveChannelSyncing = true, - }; - irc.OnChannelMessage += Irc_OnChannelMessage; - irc.OnQueryMessage += Irc_OnQueryMessage; - } - - /// - public string SendMessageDirect(string message, string channel) - { - try - { - if (!Connected()) - return "Disconnected."; - lock (IRCLock) - { - SendType st; - if (channel.Contains(PrivateMessageMarker)) - { - channel = channel.Replace(PrivateMessageMarker, ""); - st = SendType.Notice; - } - else - st = SendType.Message; - irc.SendMessage(st, channel, message); - } - return null; - } - catch (Exception e) - { - return e.ToString(); - } - } - - /// - public string SetProviderInfo(ChatSetupInfo info) - { - var convertedInfo = (IRCSetupInfo)info; - var serverChange = convertedInfo.URL != IRCConfig.URL || convertedInfo.Port != IRCConfig.Port; - IRCConfig = convertedInfo; - if (!IRCConfig.Enabled) - { - Disconnect(); - return null; - } - else if (serverChange || !Connected()) - return Reconnect(); - - if (IRCConfig.Nickname != irc.Nickname) - irc.RfcNick(convertedInfo.Nickname); - Login(); - JoinChannels(); - return null; - } - - /// - /// Checks if a message is considered sent from a chat admin - /// - /// The - /// if was sent by a chat admin, otherwise - private bool CheckAdmin(IrcMessageData e) - { - if (IRCConfig.AdminsAreSpecial) - { - if (e.Channel == null) - return false; - var user = (NonRfcChannelUser)irc.GetChannelUser(e.Channel, e.Nick); - if (user != null) - switch (IRCConfig.AuthLevel) - { - case IRCMode.Voice: - if (user.IsVoice) - return true; - goto case IRCMode.Halfop; - case IRCMode.Halfop: - if (user.IsHalfop) - return true; - goto case IRCMode.Op; - case IRCMode.Op: - if (user.IsOp) - return true; - goto case IRCMode.Owner; - case IRCMode.Owner: - if (user.IsOwner) - return true; - break; - } - } - else - { - var lowerNick = e.Nick.ToLower(); - foreach (var I in IRCConfig.AdminList) - if (lowerNick == I.ToLower()) - return true; - } - return false; - } - - /// - /// Called when the bot recieves a query message - /// - /// The sender of the event (usually ) - /// The - private void Irc_OnQueryMessage(object sender, IrcEventArgs e) - { - OnChatMessage(this, e.Data.Nick, e.Data.Nick + PrivateMessageMarker, e.Data.Message, CheckAdmin(e.Data), true); - } - - /// - /// Called when a channel the bot is in recieves a message - /// - /// The sender of the event (usually ) - /// The - private void Irc_OnChannelMessage(object sender, IrcEventArgs e) - { - var formattedMessage = e.Data.Message.Trim(); - - var splits = new List(formattedMessage.Split(' ')); - var test = splits[0]; - if (test.Length > 1 && (test[test.Length - 1] == ':' || test[test.Length - 1] == ',')) - test = test.Substring(0, test.Length - 1); - test = test.ToLower(); - if (test != irc.Nickname.ToLower() && test != "!tgs") - return; - - splits.RemoveAt(0); - formattedMessage = String.Join(" ", splits); - - OnChatMessage(this, e.Data.Nick, e.Data.Channel, formattedMessage, CheckAdmin(e.Data), IRCConfig.AdminChannels.Contains(e.Data.Channel.ToLower())); - } - - /// - /// Joins all channels specified in - /// - void JoinChannels() - { - var hs = new HashSet(); //for unique inserts - foreach (var I in IRCConfig.AdminChannels) - hs.Add(I); - foreach (var I in IRCConfig.DevChannels) - hs.Add(I); - foreach (var I in IRCConfig.GameChannels) - hs.Add(I); - foreach (var I in IRCConfig.WatchdogChannels) - hs.Add(I); - var ToPart = new List(); - foreach (var I in irc.JoinedChannels) - if (!hs.Remove(I)) - ToPart.Add(I); - foreach (var I in ToPart) - irc.RfcPart(I); - foreach (var I in hs) - irc.RfcJoin(I); - } - - /// - /// Sends a login query to with message - /// - void Login() - { - lock (IRCLock) - { - if (IRCConfig.AuthTarget != null) - irc.SendMessage(SendType.Message, IRCConfig.AuthTarget, IRCConfig.AuthMessage); - } - } - /// - public string Connect() - { - if (Connected() || !IRCConfig.Enabled) - return null; - lock (IRCLock) - { - try - { - try - { - irc.Connect(IRCConfig.URL, IRCConfig.Port); - } - catch (Exception e) - { - return "IRC server unreachable: " + e.ToString(); - } - - try - { - irc.Login(IRCConfig.Nickname, IRCConfig.Nickname); - } - catch (Exception e) - { - return "Bot name is already taken: " + e.ToString(); - } - Login(); - JoinChannels(); - - new Thread(new ThreadStart(IRCListen)) { IsBackground = true }.Start(); - return null; - } - catch (Exception e) - { - return e.ToString(); - } - } - } - - /// - /// Runs the listener in a safe loop - /// - void IRCListen() - { - Thread.CurrentThread.Name = "IRC Listener Thread"; - while (irc != null && Connected()) - try - { - irc.Listen(); - } - catch { } - } - - /// - public string Reconnect() - { - Disconnect(); - return Connect(); - } - - /// - public void Disconnect() - { - try - { - lock (IRCLock) - { - if (irc.IsConnected) - { - irc.RfcQuit("Twas meant to be..."); - irc.Disconnect(); - } - } - } - catch { } - } - /// - public bool Connected() - { - lock (IRCLock) - { - return irc != null && irc.IsConnected; - } - } - /// - public void SendMessage(string message, MessageType mt) - { - if (!Connected()) - return; - lock (IRCLock) - { - string cids = ""; - for (var I = 0; I < irc.JoinedChannels.Count; ++I) - { - var cid = irc.JoinedChannels[I]; - bool SendToThisChannel = (mt.HasFlag(MessageType.AdminInfo) && IRCConfig.AdminChannels.Contains(cid)) - || (mt.HasFlag(MessageType.DeveloperInfo) && IRCConfig.DevChannels.Contains(cid)) - || (mt.HasFlag(MessageType.GameInfo) && IRCConfig.GameChannels.Contains(cid)) - || (mt.HasFlag(MessageType.WatchdogInfo) && IRCConfig.WatchdogChannels.Contains(cid)); - if (SendToThisChannel) - cids += I > 0 ? ',' + cid : cid; - } - irc.SendMessage(SendType.Message, cids, message); - } - } - - - #region IDisposable Support - /// - /// To detect redundant calls - /// - private bool disposedValue = false; - - /// - /// Implements the pattern. Calls and sets to - /// - /// if was called manually, if it was from the finalizer - void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - Disconnect(); - irc = null; - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~TGIRCChatProvider() { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - /// - /// Implements the pattern - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - } -} diff --git a/legacy/V3/TGS.Server/DeprecatedInstanceConfig.cs b/legacy/V3/TGS.Server/DeprecatedInstanceConfig.cs deleted file mode 100644 index 8f06074e44..0000000000 --- a/legacy/V3/TGS.Server/DeprecatedInstanceConfig.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Configuration; - -namespace TGS.Server -{ - /// - /// Used to migrate old config settings - /// - class DeprecatedInstanceConfig : InstanceConfig - { - /// - /// The default directory - /// - const string DefaultInstallationPath = "C:\\TGSTATION-SERVER-3"; - - /// - /// Construct a . Used by the deserializer - /// - [Obsolete("This method is for use by the deserializer only.", true)] - public DeprecatedInstanceConfig() : base(DefaultInstallationPath) { } - - /// - /// Construct a for a at - /// - /// The path to the - public DeprecatedInstanceConfig(string path) : base(path) { } - - /// - /// Migrates the from to - /// - public void MigrateToCurrentVersion() - { - for (; Version < CurrentVersion; ++Version) - Migrate(); - } - - /// - /// Migrates the from to + 1 - /// - void Migrate() - { - //Not needed so far - } - } -} diff --git a/legacy/V3/TGS.Server/EventID.cs b/legacy/V3/TGS.Server/EventID.cs deleted file mode 100644 index 9981fd199f..0000000000 --- a/legacy/V3/TGS.Server/EventID.cs +++ /dev/null @@ -1,335 +0,0 @@ -using System; - -namespace TGS.Server -{ - /// - /// Various events and their IDs in no particular order. Found in the Windows event log. These key incremented by 100 and are guaranteed to never be reused in the future. In the windows event viewer, these IDs will be offset by the to distinguish events between instances. Each event ID may be information, a warning, or error and will be documented accordingly. Warnings will occur due to user, data, or network errors. Errors will occur due to filesystem errors or hard faults - /// - public enum EventID : int - { - /// - /// Info: When the bot recieves a (not necessarily valid) chat command - /// - ChatCommand = 100, - /// - /// Warning: When a fails to - /// - ChatConnectFail = 200, - /// - /// Error: When a fails to construct - /// - ChatProviderStartFail = 300, - /// - /// Error: When a bad is passed - /// - InvalidChatProvider = 400, - /// - /// Not in use anymore - /// - [Obsolete("Not in use anymore", true)] - UpdateRequest = 500, - /// - /// Warning: When the BYOND updater cannot download a revision. Error: When the BYOND updater cannot unzip or apply a revision - /// - BYONDUpdateFail = 600, - /// - /// Info: When the BYOND updater successfully staged a revision but could not apply it due to the being active - /// - BYONDUpdateStaged = 700, - /// - /// Info: When the BYOND updater successfully applies an update - /// - BYONDUpdateComplete = 800, - /// - /// Error: Failed to move the with TGS.Interface.Components.ITGAdministration.MoveServer(string) - /// - [Obsolete("Not in use anymore", true)] - ServerMoveFailed = 900, - /// - /// Warning: Failed to delete the old directory during a TGS.Interface.Components.ITGAdministration.MoveServer(string) operation - /// - [Obsolete("Not in use anymore", true)] - ServerMovePartial = 1000, - /// - /// Info: Successful completion of a TGS.Interface.Components.ITGAdministration.MoveServer(string) operation - /// - [Obsolete("Not in use anymore", true)] - ServerMoveComplete = 1100, - /// - /// Error: An internal error occurred during a operation - /// - DMCompileCrash = 1200, - /// - /// Error: An internal error occurred during a operation - /// - DMInitializeCrash = 1300, - /// - /// Warning: Compile failure of the target .dme in a operation - /// - DMCompileError = 1400, - /// - /// Info: Successful completion of a operation - /// - DMCompileSuccess = 1500, - /// - /// Info: Successful completion of a operation - /// - DMCompileCancel = 1600, - /// - /// Error: Failed to reattach the watchdog to a running DreamDaemon instance after a update - /// - DDReattachFail = 1700, - /// - /// Info: Successfully reattached the watchdog to a running DreamDaemon instance after a update - /// - DDReattachSuccess = 1800, - /// - /// Error: An internal error occurred while running the DreamDaemon watchdog - /// - DDWatchdogCrash = 1900, - /// - /// Info: The watchdog has exited, either for an or , or operation - /// - DDWatchdogExit = 2000, - /// - /// Not in use anymore - /// - [Obsolete("Not in use anymore", true)] - DDWatchdogRebootedServer = 2100, - /// - /// Warning: DreamDaemon has crashed and the watchdog is attempting to reboot it - /// - DDWatchdogRebootingServer = 2200, - /// - /// Info: The watchdog is performing a operation - /// - DDWatchdogRestart = 2300, - /// - /// Info: Successful completion of a operation - /// - DDWatchdogRestarted = 2400, - /// - /// Info: Successful completion of a operation - /// - DDWatchdogStarted = 2500, - /// - /// Info: Successful completion of a operation - /// - [Obsolete("Not in use anymore", true)] - ChatSend = 2600, - /// - /// Info: Successful completion of a operation - /// - [Obsolete("Not in use anymore", true)] - ChatBroadcast = 2700, - /// - /// Not in use anymore - /// - [Obsolete("Not in use anymore", true)] - ChatAdminBroadcast = 2800, - /// - /// Error: When an error occurs during a operation - /// - [Obsolete("Not in use anymore", true)] - ChatDisconnectFail = 2900, - /// - /// Not in use anymore - /// - [Obsolete("Not in use anymore", true)] - TopicSent = 3000, - /// - /// Not in use anymore - /// - [Obsolete("Not in use anymore", true)] - TopicFailed = 3100, - /// - /// Info: When the has been generated - /// - CommsKeySet = 3200, - /// - /// Not in use anymore - /// - [Obsolete("Not in use anymore", true)] - NudgeStartFail = 3300, - /// - /// Not in use anymore - /// - [Obsolete("Not in use anymore", true)] - NudgeCrash = 3400, - /// - /// Info: Successful completion of a operation - /// - RepoClone = 3500, - /// - /// Warning: An error occurred during a operation - /// - RepoCloneFail = 3600, - /// - /// Info: Successful completion of a operation - /// - RepoCheckout = 3700, - /// - /// Warning: An error occurred during a operation - /// - RepoCheckoutFail = 3800, - /// - /// Info: Successful completion of a operation with a parameter - /// - RepoHardUpdate = 3900, - /// - /// Warning: An error occurred during a operation with a parameter - /// - RepoHardUpdateFail = 4000, - /// - /// Info: Successful completion of a operation with a parameter - /// - RepoMergeUpdate = 4100, - /// - /// Warning: An error occurred during a operation with a parameter - /// - RepoMergeUpdateFail = 4200, - /// - /// Info: A backup tag of the repository in it's current state was successfully created - /// - RepoBackupTag = 4300, - /// - /// Warning: A backup tag of the repository in it's current state failed to be created - /// - RepoBackupTagFail = 4400, - /// - /// Info: Successful completion of a operation with a parameter - /// - RepoResetTracked = 4500, - /// - /// Warning: An error occurred during a operation with a parameter - /// - RepoResetTrackedFail = 4600, - /// - /// Info: Successful completion of a operation with a parameter - /// - RepoReset = 4700, - /// - /// Warning: An error occurred during a operation with a parameter - /// - RepoResetFail = 4800, - /// - /// Error: Failed to update or delete the testmerged PR list - /// - RepoPRListError = 4900, - /// - /// Info: Successful completion of a operation - /// - RepoPRMerge = 5000, - /// - /// Warning: An error occurred during a operation - /// - RepoPRMergeFail = 5100, - /// - /// Info: Successfully committed the paths specified in the synchronize_directories field of the 's TGS3.json - /// - RepoCommit = 5200, - /// - /// Warning: An error occurred while committing the paths specified in the synchronize_directories field of the 's TGS3.json - /// - RepoCommitFail = 5300, - /// - /// Info: Successful completion of a operation - /// - RepoPush = 5400, - /// - /// Warning: An error occurred during a operation - /// - RepoPushFail = 5500, - /// - /// Info: Successful completion of a operation - /// - RepoChangelog = 5600, - /// - /// Warning: An error occurred during a operation - /// - RepoChangelogFail = 5700, - /// - /// Info: When the dll is updated for the - /// - BridgeDLLUpdated = 5800, - /// - /// Error: An error occurred while updating the dll for the - /// - BridgeDLLUpdateFail = 5900, - /// - /// Error: An error occurred while starting the - /// - InstanceInitializationFailure = 6000, - /// - /// Error: When an exception occurs while the is stopping - /// - ServiceShutdownFail = 6100, - /// - /// Info: When the reboots in BYOND - /// - WorldReboot = 6200, - /// - /// Info: When the output of is applied to the live - /// - ServerUpdateApplied = 6300, - /// - /// Warning: When an exception occurs during a operation - /// - ChatBroadcastFail = 6400, - /// - /// Not in use anymore - /// - [Obsolete("Not in use anymore", true)] - IRCLogModes = 6500, - /// - /// Warning: When the Repository submodule handler has to reclone a submodule entirely. This is a long operation and is due to an upstream bug - /// Error: When a submodule update operation fails completely - /// - Submodule = 6600, - /// - /// This event is of type or . It occurs when a user different from the previous one tries and either succeeds or fails to access a . DreamDaemon itself successfully accessing will not trigger this - /// - Authentication = 6700, - /// - /// Info: When a Preaction event successfully completes - /// - PreactionEvent = 6800, - /// - /// Warning: When a Preaction event fails - /// - PreactionFail = 6900, - /// - /// Warning: When a command from DreamDaemon fails - /// - InteropCallException = 7000, - /// - /// Warning: When the running DreamDaemon code does not have the correct API to talk to the - /// - APIVersionMismatch = 7100, - /// - /// Warning: If a .dll in the repository's TGS3.json could not be found. Error: If a symlink could not be established to a static .dll or directory - /// - RepoConfigurationFail = 7200, - /// - /// Info: Successfully read of a static path. Warning: An error occurred during a read of a static path - /// - StaticRead = 7300, - /// - /// Info: Successfully write of a static path. Warning: An error occurred during a write of a static path - /// - StaticWrite = 7400, - /// - /// Info: Successfully delete of a static path. Warning: An error occurred during a delete of a static path - /// - StaticDelete = 7500, - /// - /// Info: When an instance's logging ID is first assigned - /// - InstanceIDAssigned = 7600, - /// - /// Info: When a testmerge commit is published - /// Warning: When a testmerge commit failed to be published - /// - ReferencePush = 7700, - } -} diff --git a/legacy/V3/TGS.Server/Helpers.cs b/legacy/V3/TGS.Server/Helpers.cs deleted file mode 100644 index 4dc3f4c1e9..0000000000 --- a/legacy/V3/TGS.Server/Helpers.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace TGS.Server -{ - /// - /// Generic helpers for the - /// - static class Helpers - { - /// - /// Copy a file from to , but first ensure the destination directory exists - /// - /// The source file - /// The destination file - /// If , will overwrite if it is a file. Otherwise, if exists, an exception will be thrown - public static void CopyFileForceDirectories(string source, string dest, bool overwrite) - { - try - { - Directory.CreateDirectory(Path.GetDirectoryName(dest)); - } - catch { } //we don't care if the above errors - File.Copy(source, dest, overwrite); //if this throws errors thats all we care about - } - - //http://stackoverflow.com/questions/1701457/directory-delete-doesnt-work-access-denied-error-but-under-windows-explorer-it - /// - /// Recursive directory deleter - /// - /// The directory to delete - /// If , an empty will remain instead of being deleted fully. Incompatible with - /// If any files or directories in the root level of match anything in this of s, they won't be deleted. Incompatible with - public static void DeleteDirectory(string path, bool ContentsOnly = false, IList excludeRoot = null) - { - if (!ContentsOnly && excludeRoot != null && excludeRoot.Count > 0) - throw new ArgumentException("Cannot fully delete folder with exclusions specified!"); - var di = new DirectoryInfo(path); - if (!di.Exists) - return; - if (excludeRoot != null) - for (var I = 0; I < excludeRoot.Count; ++I) - excludeRoot[I] = excludeRoot[I].ToLower(); - if (CheckDeleteSymlinkDir(di)) - return; - NormalizeAndDelete(di, excludeRoot, !ContentsOnly).Wait(); - } - - /// - /// Find all files in a directory with a given extension - /// - /// The directory to search - /// The extension to look for - /// An of s containing the full paths to files with the given in - public static IEnumerable GetFilesWithExtensionInDirectory(string directory, string extension) - { - if (directory == null) - throw new ArgumentNullException(nameof(directory)); - if (extension == null) - throw new ArgumentNullException(nameof(extension)); - - var di = new DirectoryInfo(directory); - if (!di.Exists) - yield break; - - foreach (var F in di.EnumerateFiles(String.Format(CultureInfo.InvariantCulture, "*.{0}", extension))) - yield return F.FullName; - } - - /// - /// Properly unlinks directory if it is a symlink - /// - /// for the directory in question - /// if was a symlink and deleted, otherwise - static bool CheckDeleteSymlinkDir(DirectoryInfo di) - { - if (!di.Attributes.HasFlag(FileAttributes.Directory) || di.Attributes.HasFlag(FileAttributes.ReparsePoint)) - { //this is probably a symlink - Directory.Delete(di.FullName); - return true; - } - return false; - } - - /// - /// Recursively empty a directory - /// - /// of the directory to empty - /// Lowercase file and directory names to skip while emptying this level. Not passed forward - /// If , deletes on completion - /// A representing the operation - static async Task NormalizeAndDelete(DirectoryInfo dir, IList excludeRoot, bool deleteRoot) - { - var tasks = new List(); - foreach (var subDir in dir.GetDirectories()) - { - var upperName = subDir.Name.ToUpperInvariant(); - if (excludeRoot != null && excludeRoot.Any(x => x.ToUpperInvariant() == upperName)) - continue; - if (CheckDeleteSymlinkDir(subDir)) - continue; - tasks.Add(NormalizeAndDelete(subDir, null, true)); - } - foreach (var file in dir.GetFiles()) - { - var upperName = file.Name.ToUpperInvariant(); - if (excludeRoot != null && excludeRoot.Any(x => x.ToUpperInvariant() == upperName)) - continue; - file.Attributes = FileAttributes.Normal; - file.Delete(); - } - await Task.WhenAll(tasks); - if(deleteRoot) - dir.Delete(true); - } - - /// - /// Recusively copy a directory - /// - /// The directory to copy - /// The destination directory - /// List of files and directories to ignore while copying - /// If no error will be thrown if does not exist - public static void CopyDirectory(string sourceDirName, string destDirName, IList ignore = null, bool ignoreIfNotExists = false) - { - IList realIgnore; - if (ignore != null) - { - realIgnore = new List(); - foreach (var I in ignore) - realIgnore.Add(I.ToLower()); - } - else - realIgnore = null; - CopyDirectoryImpl(sourceDirName, destDirName, realIgnore, ignoreIfNotExists).Wait(); - } - - /// - /// Recusively copy a directory - /// - /// The directory to copy - /// The destination directory - /// List of lowercase files and directories to ignore while copying - /// If no error will be thrown if does not exist - /// A representing the operation - static async Task CopyDirectoryImpl(string sourceDirName, string destDirName, IList ignore, bool ignoreIfNotExists) { - // If the destination directory doesn't exist, create it. - Directory.CreateDirectory(destDirName); - // Get the subdirectories for the specified directory. - DirectoryInfo dir = new DirectoryInfo(sourceDirName); - - if (!dir.Exists) - { - if (ignoreIfNotExists) - return; - throw new DirectoryNotFoundException( - "Source directory does not exist or could not be found: " - + sourceDirName); - } - - var tasks = new List(); - DirectoryInfo[] dirs = dir.GetDirectories(); - // copy them and their contents to new location. - foreach (DirectoryInfo subdir in dirs) - { - var upperName = subdir.Name.ToUpperInvariant(); - if (ignore != null && ignore.Any(x => x.ToUpperInvariant() == upperName)) - continue; - string temppath = Path.Combine(destDirName, subdir.Name); - tasks.Add(CopyDirectoryImpl(subdir.FullName, temppath, ignore, false)); - } - - // Get the files in the directory and copy them to the new location. - FileInfo[] files = dir.GetFiles(); - foreach (FileInfo file in files) - { - var upperName = file.Name.ToUpperInvariant(); - if (ignore != null && ignore.Any(x => x.ToUpperInvariant() == upperName)) - continue; - string temppath = Path.Combine(destDirName, file.Name); - file.CopyTo(temppath, true); - } - - await Task.WhenAll(tasks); - } - - /// - /// Properly escapes characters for a BYOND Topic() packet. See http://www.byond.com/docs/ref/info.html#/proc/list2params - /// - /// The to sanitize - /// The sanitized string - public static string SanitizeTopicString(string input) - { - return input.Replace("%", "%25").Replace("=", "%3d").Replace(";", "%3b").Replace("&", "%26").Replace("+", "%2b"); - } - - /// - /// Normalizes different versions of a path - /// - /// The path to normalize - /// The normalized path - public static string NormalizePath(string path) - { - return Path.GetFullPath(new Uri(path).LocalPath) - .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) - .ToUpperInvariant(); - } - } -} diff --git a/legacy/V3/TGS.Server/ILogger.cs b/legacy/V3/TGS.Server/ILogger.cs deleted file mode 100644 index 9615b133b7..0000000000 --- a/legacy/V3/TGS.Server/ILogger.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace TGS.Server -{ - /// - /// Used for writing logs to a provider - /// - public interface ILogger - { - /// - /// Writes information to the log - /// - /// The log message - /// The of the message - /// The 0-99 ID of the log source - void WriteInfo(string message, EventID id, byte loggingID); - - /// - /// Writes an error to the log - /// - /// The log message - /// The of the message - /// The 0-99 ID of the log source - void WriteError(string message, EventID id, byte loggingID); - - /// - /// Writes a warning to the log - /// - /// The log message - /// The of the message - /// The 0-99 ID of the log source - void WriteWarning(string message, EventID id, byte loggingID); - - /// - /// Writes an access event to the log - /// - /// The (un)authenticated Windows user's name - /// if authenticated sucessfully, otherwise - /// The 0-99 ID of the log source - void WriteAccess(string username, bool authSuccess, byte loggingID); - } -} diff --git a/legacy/V3/TGS.Server/Instance/Administration.cs b/legacy/V3/TGS.Server/Instance/Administration.cs deleted file mode 100644 index 20b7ad4ded..0000000000 --- a/legacy/V3/TGS.Server/Instance/Administration.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.DirectoryServices.AccountManagement; -using System.Security.Principal; -using System.ServiceModel; -using System.Threading; -using TGS.Interface; -using TGS.Interface.Components; -using TGS.Server.Security; - -namespace TGS.Server -{ - sealed partial class Instance : ServiceAuthorizationManager, ITGAdministration - { - /// - /// The of the Windows group authorized to access the - /// - SecurityIdentifier TheDroidsWereLookingFor; - /// - /// Used for multithreading safety - /// - object authLock = new object(); - /// - /// The of the last to attempt to access the - /// - string LastSeenUser; - - /// - /// The of the account the is running as - /// - readonly SecurityIdentifier ServiceSID = WindowsIdentity.GetCurrent().User; - - void InitAdministration() - { - lock(RootAuthorizationManager.InstanceAuthManagers) - RootAuthorizationManager.InstanceAuthManagers.Add(this); - FindTheDroidsWereLookingFor(); - } - - /// - public string GetCurrentAuthorizedGroup() - { - try - { - if (TheDroidsWereLookingFor == null) - return "ADMIN"; - - string res = null; - try - { - res = GroupPrincipal.FindByIdentity(new PrincipalContext(ContextType.Machine), IdentityType.Sid, TheDroidsWereLookingFor.Value).Name; - } - catch { } - return res ?? GroupPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain), IdentityType.Sid, TheDroidsWereLookingFor.Value).Name; - } - catch - { - return null; - } - } - - /// - public string SetAuthorizedGroup(string groupName) - { - if (groupName == null) - { - TheDroidsWereLookingFor = null; - Config.AuthorizedUserGroupSID = null; - Config.Save(); - return "ADMIN"; - } - return FindTheDroidsWereLookingFor(groupName); - } - - /// - /// Set based off either an ed name or a string from the config - /// - /// The name of the group to search for - /// Recursive parameter used to check for the group using instead of - /// The name of the group allowed to access the if it could be found, otherwise - string FindTheDroidsWereLookingFor(string search = null, bool useDomain = false) - { - //find the group that is authorized to use the tools - var pc = new PrincipalContext(useDomain ? ContextType.Domain : ContextType.Machine); - var groupName = search ?? Config.AuthorizedUserGroupSID; - if (String.IsNullOrWhiteSpace(groupName)) - return null; - var gp = GroupPrincipal.FindByIdentity(pc, search != null ? IdentityType.Name : IdentityType.Sid, groupName); - if (gp == null) - { - if (search != null) - //try again with all types - gp = GroupPrincipal.FindByIdentity(pc, search); - if (gp == null) - return useDomain ? null : FindTheDroidsWereLookingFor(search, true); - } - TheDroidsWereLookingFor = gp.Sid; - if (search != null) - { - Config.AuthorizedUserGroupSID = TheDroidsWereLookingFor.Value; - Config.Save(); - } - return gp.Name; - } - - /// - /// Cleans up the component - /// - void DisposeAdministration() - { - lock(RootAuthorizationManager.InstanceAuthManagers) - RootAuthorizationManager.InstanceAuthManagers.Remove(this); - } - - /// - /// Called by WCF whenever a component call is made. Checks to see that the supplied user account has access to the requested component - /// - /// Various parameters about the operation supplied by WCF - /// if the supplied user account may use the requested component, otherwise - protected override bool CheckAccessCore(OperationContext operationContext) - { - var contract = operationContext.EndpointDispatcher.ContractName; - - if (contract == typeof(ITGConnectivity).Name) //always allow connectivity checks - return true; - - var windowsIdent = operationContext.ServiceSecurityContext.WindowsIdentity; - - if (contract == typeof(ITGInterop).Name) - { - //only allow the same user the service is running as to use interop, because that's what DD is running as, and don't spam the logs with it unless it fails - var result = windowsIdent.User == ServiceSID; - if(!result) - WriteAccess(windowsIdent.Name, false); - return result; - } - - var wp = new WindowsPrincipal(windowsIdent); - //first allow admins - var authSuccess = wp.IsInRole(WindowsBuiltInRole.Administrator); - - //if we're not an admin, check that we aren't trying to access the admin interface - if (!authSuccess && operationContext.EndpointDispatcher.ContractName != typeof(ITGAdministration).Name && TheDroidsWereLookingFor != null) - authSuccess = wp.IsInRole(new SecurityIdentifier(Config.AuthorizedUserGroupSID)); - - lock (authLock) - { - var user = windowsIdent.Name; - if (LastSeenUser != user) - { - LastSeenUser = user; - WriteAccess(user, authSuccess); - } - } - return authSuccess; - } - - public string RecreateStaticFolder() - { - lock (RepoLock) - { - if (RepoBusy) - return "Repo locked!"; - RepoBusy = true; - } - try - { - if (!Monitor.TryEnter(watchdogLock)) - return "Watchdog locked!"; - try - { - if (!Monitor.TryEnter(configLock)) - return "Static dir locked!"; - try - { - if (currentStatus != DreamDaemonStatus.Offline) - return "Watchdog running!"; - BackupAndDeleteStaticDirectory(); - InitialConfigureRepository(); - } - finally - { - Monitor.Exit(configLock); - } - } - finally - { - Monitor.Exit(watchdogLock); - } - } - catch(Exception e) - { - return e.ToString(); - } - finally - { - lock (RepoLock) - RepoBusy = false; - } - return null; - } - } -} diff --git a/legacy/V3/TGS.Server/Instance/Byond.cs b/legacy/V3/TGS.Server/Instance/Byond.cs deleted file mode 100644 index 768443aa77..0000000000 --- a/legacy/V3/TGS.Server/Instance/Byond.cs +++ /dev/null @@ -1,381 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Text.RegularExpressions; -using System.Threading; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.Server -{ - sealed partial class Instance : ITGByond - { - /// - /// The instance directory to store the BYOND installation - /// - const string ByondDirectory = "BYOND"; - /// - /// The instance directory to use when updating the BYOND installation - /// - const string StagingDirectory = "BYOND_staged"; - /// - /// Path to the actual BYOND installation within the - /// - const string StagingDirectoryInner = StagingDirectory + "/byond"; - /// - /// The path in the instance directory to store the downloaded BYOND revision - /// - const string RevisionDownloadPath = "BYONDRevision.zip"; - /// - /// The location of the BYOND version data of an installation - /// - const string VersionFile = "/byond_version.dat"; - /// - /// The URL format string for getting BYOND version {0}.{1} zipfile - /// - const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; - /// - /// The URL for getting the latest BYOND version zipfile - /// - const string ByondLatestURL = "https://secure.byond.com/download/build/LATEST/"; - /// - /// The instance directory to modify the BYOND cfg before installation - /// - const string ByondConfigDir = StagingDirectory + "/BYOND/cfg"; - /// - /// BYOND's DreamDaemon config file in the cfg modification directory - /// - const string ByondDDConfig = ByondConfigDir + "/daemon.txt"; - /// - /// Setting to add to to suppress an invisible user prompt for running a trusted mode .dmb - /// - const string ByondNoPromptTrustedMode = "trusted-check 0"; - - /// - /// Contains cache shit that needs to be cleared out every startup - /// - static readonly string ByondCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), ByondDirectory, "cache"); - - /// - /// The status of the BYOND updater - /// - ByondStatus updateStat = ByondStatus.Idle; - - /// - /// Used for multithreading safety - /// - object ByondLock = new object(); - /// - /// The last error the BYOND updater encountered - /// - string lastError; - /// - /// Thread used for staging BYOND revisions - /// - Thread RevisionStaging; - - /// - /// Called when the is setup. Prepares the BYOND updater - /// - void InitByond() - { - CleanByondStaging(); - } - - /// - /// Cleans the BYOND staging directory - /// - void CleanByondStaging() - { - var rrdp = RelativePath(RevisionDownloadPath); - //linger not - if (File.Exists(rrdp)) - File.Delete(rrdp); - Helpers.DeleteDirectory(RelativePath(StagingDirectory)); - } - - /// - /// Called when the is shutdown - /// - void DisposeByond() - { - lock (ByondLock) - { - if (RevisionStaging != null) - RevisionStaging.Abort(); - CleanByondStaging(); - } - } - - /// - /// Attempts to clear the - /// - void ClearCacheFolder() - { - try - { - Helpers.DeleteDirectory(ByondCacheDirectory, true, null); - } - catch { } - } - - /// - /// Checks if the updater is considered busy - /// - /// if the updater is considered busy, otherwise - bool BusyCheck() - { - lock (ByondLock) - switch (updateStat) - { - default: - case ByondStatus.Starting: - case ByondStatus.Downloading: - case ByondStatus.Staging: - case ByondStatus.Updating: - case ByondStatus.CompilingStaged: - return true; - case ByondStatus.Idle: - case ByondStatus.Staged: - return false; - } - } - - /// - public ByondStatus CurrentStatus() - { - lock (ByondLock) - { - return updateStat; - } - } - - /// - public string GetError() - { - lock (ByondLock) - { - var error = lastError; - lastError = null; - return error; - } - } - - /// - public string GetVersion(ByondVersion type) - { - try - { - lock (ByondLock) - { - if (type == ByondVersion.Latest) - { - //get the latest version from the website - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ByondLatestURL); - var results = new List(); - using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) - { - using (StreamReader reader = new StreamReader(response.GetResponseStream())) - { - string html = reader.ReadToEnd(); - - Regex regex = new Regex("\\\"([^\"]*)\\\""); - MatchCollection matches = regex.Matches(html); - foreach (Match match in matches) - if (match.Success && match.Value.Contains("_byond.exe")) - results.Add(match.Value.Replace("\"", "").Replace("_byond.exe", "")); - } - } - results.Sort(); - results.Reverse(); - return results.Count > 0 ? results[0] : null; - } - else - { - string DirToUse = RelativePath(type == ByondVersion.Staged ? StagingDirectoryInner : ByondDirectory); - if (Directory.Exists(DirToUse)) - { - string file = DirToUse + VersionFile; - if (File.Exists(file)) - return File.ReadAllText(file); - } - } - return null; - } - } - catch (Exception e) - { - return "Error: " + e.ToString(); - } - } - - /// - /// Downloads and unzips a BYOND revision. Calls afterwards if the isn't running, otherwise, calls . Sets on failure - /// - /// Stringified BYOND revision - public void UpdateToVersionImpl(object param) - { - lock (ByondLock) { - if (updateStat != ByondStatus.Starting) - return; - updateStat = ByondStatus.Downloading; - } - bool staged = false; - try - { - CleanByondStaging(); - - var vi = ((string)param).Split('.'); - var major = Convert.ToInt32(vi[0]); - var minor = Convert.ToInt32(vi[1]); - var rrdp = RelativePath(RevisionDownloadPath); - using (var client = new WebClient()) - { - SendMessage(String.Format("BYOND: Updating to version {0}.{1}...", major, minor), MessageType.DeveloperInfo); - - //DOWNLOADING - - try - { - client.DownloadFile(String.Format(ByondRevisionsURL, major, minor), rrdp); - } - catch - { - SendMessage("BYOND: Update download failed. Does the specified version exist?", MessageType.DeveloperInfo); - lastError = String.Format("Download of BYOND version {0}.{1} failed! Does it exist?", major, minor); - WriteWarning(String.Format("Failed to update BYOND to version {0}.{1}!", major, minor), EventID.BYONDUpdateFail); - lock (ByondLock) - { - updateStat = ByondStatus.Idle; - } - return; - } - } - lock (ByondLock) - { - updateStat = ByondStatus.Staging; - } - - //STAGING - - ZipFile.ExtractToDirectory(rrdp, RelativePath(StagingDirectory)); - lock (ByondLock) - { - File.WriteAllText(RelativePath(StagingDirectoryInner + VersionFile), String.Format("{0}.{1}", major, minor)); - //IMPORTANT: SET THE BYOND CONFIG TO NOT PROMPT FOR TRUSTED MODE REEE - Directory.CreateDirectory(RelativePath(ByondConfigDir)); - File.WriteAllText(RelativePath(ByondDDConfig), ByondNoPromptTrustedMode); - } - File.Delete(RevisionDownloadPath); - - lock (ByondLock) - { - updateStat = ByondStatus.Staged; - } - - switch (DaemonStatus()) - { - case DreamDaemonStatus.Offline: - if(ApplyStagedUpdate()) - lastError = null; - else - lastError = "Failed to apply update!"; - break; - default: - //Compile() will call RequestRestart() - lastError = "Update staged. Awaiting server restart..."; - SendMessage(String.Format("BYOND: Staging complete. Awaiting server restart...", major, minor), MessageType.DeveloperInfo); - WriteInfo(String.Format("BYOND update {0}.{1} staged", major, minor), EventID.BYONDUpdateStaged); - staged = true; - break; - } - } - catch (ThreadAbortException) - { - return; - } - catch (Exception e) - { - WriteError("Revision staging errror: " + e.ToString(), EventID.BYONDUpdateFail); - lock (ByondLock) - { - updateStat = ByondStatus.Idle; - lastError = e.ToString(); - RevisionStaging = null; - } - } - if (staged) - lock (ByondLock) - { - //Some fuckery to trick the compiler into starting even though we're staged - //Won't fuck up the actual compiler thread though - updateStat = ByondStatus.Idle; - Compile(); - updateStat = ByondStatus.Staged; - } - } - /// - public bool UpdateToVersion(int major, int minor) - { - lock (ByondLock) - { - if (!BusyCheck()) - { - updateStat = ByondStatus.Starting; - RevisionStaging = new Thread(new ParameterizedThreadStart(UpdateToVersionImpl)) - { - IsBackground = true //don't slow me down - }; - RevisionStaging.Start(String.Format("{0}.{1}", major, minor)); - return true; - } - return false; - } - } - - /// - /// Attempts to move the staged update from to . Sets on failure - /// - /// on success, on failure - bool ApplyStagedUpdate() - { - lock (CompilerLock) - { - if (compilerCurrentStatus == CompilerStatus.Compiling) - return false; - lock (ByondLock) - { - if (updateStat != ByondStatus.Staged) - return false; - updateStat = ByondStatus.Updating; - } - try - { - var rbd = RelativePath(ByondDirectory); - Helpers.DeleteDirectory(rbd); - Directory.Move(RelativePath(StagingDirectoryInner), rbd); - Helpers.DeleteDirectory(RelativePath(StagingDirectory)); - lastError = null; - SendMessage("BYOND: Update completed!", MessageType.DeveloperInfo); - WriteInfo(String.Format("BYOND update {0} completed!", GetVersion(ByondVersion.Installed)), EventID.BYONDUpdateComplete); - return true; - } - catch (Exception e) - { - lastError = e.ToString(); - SendMessage("BYOND: Update failed!", MessageType.DeveloperInfo); - WriteError("BYOND update failed! Error: " + e.ToString(), EventID.BYONDUpdateFail); - return false; - } - finally - { - lock(ByondLock) { - updateStat = ByondStatus.Idle; - } - } - } - } - } -} diff --git a/legacy/V3/TGS.Server/Instance/Chat.cs b/legacy/V3/TGS.Server/Instance/Chat.cs deleted file mode 100644 index 75ebc44ea6..0000000000 --- a/legacy/V3/TGS.Server/Instance/Chat.cs +++ /dev/null @@ -1,248 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Collections.Generic; -using TGS.Server.ChatCommands; -using TGS.Server.ChatProviders; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.Server -{ - sealed partial class Instance : ITGChat - { - /// - /// Used for indicating unintialized encrypted data - /// - public const string UninitializedString = "NEEDS INITIALIZING"; - - /// - /// List of s for the - /// - IList ChatProviders; - /// - /// Used for multithreading safety - /// - object ChatLock = new object(); - - /// - /// Set up the for the - /// - public void InitChat() - { - var infos = InitProviderInfos(); - ChatProviders = new List(infos.Count); - foreach (var info in infos) - { - IChatProvider chatProvider; - try - { - switch (info.Provider) - { - case ChatProvider.Discord: - chatProvider = new DiscordChatProvider(info); - break; - case ChatProvider.IRC: - chatProvider = new IRCChatProvider(info); - break; - default: - WriteError(String.Format("Invalid chat provider: {0}", info.Provider), EventID.InvalidChatProvider); - continue; - } - } - catch (Exception e) - { - WriteError(String.Format("Failed to start chat provider {0}! Error: {1}", info.Provider, e.ToString()), EventID.ChatProviderStartFail); - continue; - } - chatProvider.OnChatMessage += ChatProvider_OnChatMessage; - var res = chatProvider.Connect(); - if (res != null) - WriteWarning(String.Format("Unable to connect to chat! Provider {0}, Error: {1}", chatProvider.GetType().ToString(), res), EventID.ChatConnectFail); - ChatProviders.Add(chatProvider); - } - } - - /// - /// Implementation of that recieves messages from all channels of all connected - /// - /// The that heard the - /// The user who wrote the - /// The channel the is from - /// The recieved message - /// if is considered a chat admin, otherwise - /// if is an admin channel, otherwise - private void ChatProvider_OnChatMessage(IChatProvider ChatProvider, string speaker, string channel, string message, bool isAdmin, bool isAdminChannel) - { - var splits = message.Trim().Split(' '); - - if (splits.Length == 1 && splits[0] == "") - { - ChatProvider.SendMessageDirect("Hi!", channel); - return; - } - - var asList = new List(splits); - - Command.OutputProcVar.Value = (m) => ChatProvider.SendMessageDirect(m, channel); - ChatCommand.CommandInfo.Value = new CommandInfo() - { - IsAdmin = isAdmin, - IsAdminChannel = isAdminChannel, - Speaker = speaker, - Server = this, - }; - WriteInfo(String.Format("Chat Command from {0} ({2}): {1}", speaker, String.Join(" ", asList), channel), EventID.ChatCommand); - if (ServerChatCommands == null) - LoadServerChatCommands(); - new RootChatCommand(ServerChatCommands).DoRun(asList); - } - - /// - /// Properly shuts down all - /// - void DisposeChat() - { - var infosList = new List>(); - - foreach (var ChatProvider in ChatProviders) - { - infosList.Add(ChatProvider.ProviderInfo().DataFields); - ChatProvider.Dispose(); - } - ChatProviders = null; - - var rawdata = JsonConvert.SerializeObject(infosList, Formatting.Indented); - - Config.ChatProviderData = Interface.Helpers.EncryptData(rawdata, out string entrp); - Config.ChatProviderEntropy = entrp; - } - - /// - public IList ProviderInfos() - { - var infosList = new List(); - foreach (var chatProvider in ChatProviders) - infosList.Add(chatProvider.ProviderInfo()); - return infosList; - } - - /// - /// Returns a list of s loaded from the config or the defaults if none are set - /// - /// A list of s loaded from the config or the defaults if none are set - IList InitProviderInfos() - { - lock (ChatLock) - { - var rawdata = Config.ChatProviderData; - if (rawdata == UninitializedString) - return new List() { new IRCSetupInfo() { Nickname = Config.Name }, new DiscordSetupInfo() }; - - string plaintext; - try - { - plaintext = Interface.Helpers.DecryptData(rawdata, Config.ChatProviderEntropy); - - var lists = JsonConvert.DeserializeObject>>(plaintext); - var output = new List(lists.Count); - var foundirc = 0; - var founddiscord = 0; - foreach (var l in lists) - { - var info = new ChatSetupInfo(l); - if (info.Provider == ChatProvider.Discord) - ++founddiscord; - else if (info.Provider == ChatProvider.IRC) - ++foundirc; - output.Add(info); - } - - if (foundirc != 1 || founddiscord != 1) - throw new Exception(); - - return output; - } - catch - { - Config.ChatProviderData = UninitializedString; - } - } - //if we get here we want to retry - return InitProviderInfos(); - } - - /// - public string SetProviderInfo(ChatSetupInfo info) - { - info.AdminList.RemoveAll(x => String.IsNullOrWhiteSpace(x)); - info.AdminChannels.RemoveAll(x => String.IsNullOrWhiteSpace(x)); - info.GameChannels.RemoveAll(x => String.IsNullOrWhiteSpace(x)); - info.DevChannels.RemoveAll(x => String.IsNullOrWhiteSpace(x)); - info.WatchdogChannels.RemoveAll(x => String.IsNullOrWhiteSpace(x)); - try - { - lock (ChatLock) - { - foreach (var ChatProvider in ChatProviders) - if (info.Provider == ChatProvider.ProviderInfo().Provider) - return ChatProvider.SetProviderInfo(info); - return "Error: Invalid provider: " + info.Provider.ToString(); - } - } - catch (Exception e) - { - return e.ToString(); - } - } - - /// - public bool Connected(ChatProvider providerType) - { - foreach (var I in ChatProviders) - if (I.ProviderInfo().Provider == providerType) - return I.Connected(); - return false; - } - - /// - /// Reconnect servers that are enabled and disconnected. Checked every time DreamDaemon reboots - /// - void ChatConnectivityCheck() - { - foreach (ChatProvider I in Enum.GetValues(typeof(ChatProvider))) - if(!Connected(I)) - Reconnect(I); - } - - /// - public string Reconnect(ChatProvider providerType) - { - foreach (var I in ChatProviders) - if (I.ProviderInfo().Provider == providerType) - return I.Reconnect(); - return "Could not find specified provider!"; - } - - /// - /// Broadcast a message to appropriate channels based on the message type - /// - /// The message to send - /// The message type - public void SendMessage(string msg, MessageType mt) - { - lock (ChatLock) - { - foreach (var ChatProvider in ChatProviders) - try - { - ChatProvider.SendMessage(msg, mt); - } - catch (Exception e) - { - WriteWarning(String.Format("Chat broadcast failed (Provider: {3}) (Flags: {0}) (Message: {1}): {2}", mt, msg, e.ToString(), ChatProvider.ProviderInfo().Provider), EventID.ChatBroadcastFail); - } - } - } - } -} diff --git a/legacy/V3/TGS.Server/Instance/Compiler.cs b/legacy/V3/TGS.Server/Instance/Compiler.cs deleted file mode 100644 index db03e40940..0000000000 --- a/legacy/V3/TGS.Server/Instance/Compiler.cs +++ /dev/null @@ -1,684 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.Server -{ - sealed partial class Instance : ITGCompiler - { - #region Win32 Shit - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymbolicLink dwFlags); - enum SymbolicLink - { - File = 0, - Directory = 1 - } - #endregion - - const string StaticDirs = "Static"; - const string StaticBackupDir = "Static_BACKUP"; - - const string GameDir = "Game"; - const string GameDirA = GameDir + "/A"; - const string GameDirB = GameDir + "/B"; - const string GameDirLive = GameDir + "/Live"; - - const string LiveFile = "/TestLive.lk"; - const string ADirTest = GameDirA + LiveFile; - const string BDirTest = GameDirB + LiveFile; - const string LiveDirTest = GameDirLive + LiveFile; - - object CompilerLock = new object(); - CompilerStatus compilerCurrentStatus; - string lastCompilerError; - - Thread CompilerThread; - bool compilationCancellationRequestation = false; - bool canCancelCompilation = false; - bool silentCompile = false; - - bool UpdateStaged = false; - - //deletes leftovers and checks current status - void InitCompiler() - { - var rldt = RelativePath(LiveDirTest); - if (File.Exists(rldt)) - File.Delete(rldt); - compilerCurrentStatus = IsInitialized(); - } - - /// - public CompilerStatus GetStatus() - { - lock (CompilerLock) - { - return compilerCurrentStatus; - } - } - - /// - public string CompileError() - { - lock (CompilerLock) - { - var err = lastCompilerError; - lastCompilerError = null; - return err; - } - } - - //kills the compiler if its running - void DisposeCompiler() - { - lock (CompilerLock) - { - if (CompilerThread == null || !CompilerThread.IsAlive) - return; - CompilerThread.Abort(); //this will safely kill dm - InitCompiler(); //also cleanup - } - } - - //translates the win32 api call into an exception if it fails - static void CreateSymlink(string link, string target) - { - if (!CreateSymbolicLink(new DirectoryInfo(link).FullName, new DirectoryInfo(target).FullName, File.Exists(target) ? SymbolicLink.File : SymbolicLink.Directory)) - throw new Exception(String.Format("Failed to create symlink from {0} to {1}! Error: {2}", target, link, Marshal.GetLastWin32Error())); - } - - //requires CompilerLock to be locked - bool CompilerIdleNoLock() - { - return compilerCurrentStatus == CompilerStatus.Uninitialized || compilerCurrentStatus == CompilerStatus.Initialized; - } - - /// - public bool Initialize() - { - lock (CompilerLock) - { - if (!CompilerIdleNoLock()) - return false; - lastCompilerError = null; - compilerCurrentStatus = CompilerStatus.Initializing; - CompilerThread = new Thread(new ThreadStart(InitializeImpl)); - CompilerThread.Start(); - return true; - } - } - - //what is says on the tin - CompilerStatus IsInitialized() - { - if (File.Exists(RelativePath(Path.Combine(GameDirLive, BridgeDLLName))) || File.Exists(RelativePath(Path.Combine(GameDirLive, Assembly.GetAssembly(typeof(IClient)).GetName().Name + ".dll")))) //its a good tell, jim - return CompilerStatus.Initialized; - return CompilerStatus.Uninitialized; - } - - //we need to remove symlinks before we can recursively delete - void CleanGameFolder() - { - var GameDirABridge = RelativePath(Path.Combine(GameDirA, BridgeDLLName)); - if (Directory.Exists(GameDirABridge)) - Directory.Delete(GameDirABridge); - - var GameDirBBridge = RelativePath(Path.Combine(GameDirB, BridgeDLLName)); - if (Directory.Exists(GameDirBBridge)) - Directory.Delete(GameDirBBridge); - - var rgdl = RelativePath(GameDirLive); - if (Directory.Exists(rgdl)) - Directory.Delete(rgdl); - } - - /// - /// Copies .dm files from to and returns #include lines for them - /// - /// The directory to copy .dm files to - /// DM #include lines for .dm files in - IEnumerable GetAndCopyIncludeLines(string stagingDir) - { - if (stagingDir == null) - throw new ArgumentNullException(nameof(stagingDir)); - var baseURI = new Uri(stagingDir); - foreach (var I in Helpers.GetFilesWithExtensionInDirectory(RelativePath(StaticDirs), "dm")) - { - var fileURI = new Uri(I); - var fileName = Path.GetFileName(I); - File.Copy(I, Path.Combine(stagingDir, fileName)); - yield return String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", fileName); - } - } - - /// - /// Adds includes for .dm files in - /// - /// The directory to operate on - /// The full path the the .dme in - void HandleDMEModifications(string stagingDir, string dmePath) - { - if (stagingDir == null) - throw new ArgumentNullException(nameof(stagingDir)); - if (dmePath == null) - throw new ArgumentNullException(nameof(dmePath)); - if (!File.Exists(dmePath)) - return; //someone else will deal with this - var newInclusions = new List(); - var newLines = new List(); - - var lines = File.ReadAllLines(dmePath).ToList(); - var initalCount = lines.Count; - var enumerator = GetAndCopyIncludeLines(stagingDir); - for (var I = 0; I < lines.Count; ++I) - { - var line = lines[I]; - if (line.Contains("BEGIN_INCLUDE")) - { - lines.InsertRange(I + 1, enumerator); - break; - } - } - if (lines.Count == initalCount) - lines.InsertRange(0, enumerator); - - if (lines.Count > initalCount) - File.WriteAllLines(dmePath, lines); - } - - //Initializing thread - public void InitializeImpl() - { - Thread.CurrentThread.Name = "Initialization Thread"; - try - { - if (DaemonStatus() != DreamDaemonStatus.Offline) - { - lock (CompilerLock) - { - lastCompilerError = "Dream daemon must not be running"; - compilerCurrentStatus = IsInitialized(); - return; - } - } - - if (!Exists()) //repo - { - lock (CompilerLock) - { - lastCompilerError = "Repository is not setup!"; - compilerCurrentStatus = IsInitialized(); - return; - } - } - - if (!RepoConfigsMatch()) - { - lock (CompilerLock) - { - lastCompilerError = "Repository TGS3.json does not match cached version! Please update the config appropriately!"; - compilerCurrentStatus = IsInitialized(); - return; - } - } - try - { - SendMessage("DM: Setting up symlinks...", MessageType.DeveloperInfo); - CleanGameFolder(); - Helpers.DeleteDirectory(RelativePath(GameDir)); - - Directory.CreateDirectory(RelativePath(GameDirA)); - Directory.CreateDirectory(RelativePath(GameDirB)); - - var rep_config = GetCachedRepoConfig(); - - if (rep_config != null) { - foreach (var I in rep_config.StaticDirectoryPaths) - CreateSymlink(RelativePath(Path.Combine(GameDirA, I)), RelativePath(Path.Combine(StaticDirs, I))); - foreach (var I in rep_config.DLLPaths) - CreateSymlink(RelativePath(Path.Combine(GameDirA, I)), RelativePath(Path.Combine(StaticDirs, I))); - } - - var rbdlln = RelativePath(BridgeDLLName); - CreateSymlink(RelativePath(Path.Combine(GameDirA, BridgeDLLName)), rbdlln); - CreateSymlink(RelativePath(Path.Combine(GameDirB, BridgeDLLName)), rbdlln); - - CreateSymlink(RelativePath(GameDirLive), RelativePath(GameDirA)); - - lock (CompilerLock) - { - compilerCurrentStatus = CompilerStatus.Compiling; - silentCompile = true; - } - } - catch (ThreadAbortException) - { - return; - } - catch (Exception e) - { - lock (CompilerLock) - { - SendMessage("DM: Setup failed!", MessageType.DeveloperInfo); - WriteError("Compiler Initialization Error: " + e.ToString(), EventID.DMInitializeCrash); - lastCompilerError = e.ToString(); - compilerCurrentStatus = CompilerStatus.Uninitialized; - return; - } - } - } - catch (ThreadAbortException) - { - return; - } - CompileImpl(); - } - - //Returns the A or B dir in which the game is NOT running - string GetStagingDir() - { - string TheDir; - if (!Directory.Exists(RelativePath(GameDirLive))) - TheDir = GameDirA; - else - { - File.Create(RelativePath(LiveDirTest)).Close(); - try - { - if (File.Exists(RelativePath(ADirTest))) - TheDir = GameDirA; - else if (File.Exists(RelativePath(BDirTest))) - TheDir = GameDirB; - else - throw new Exception("Unable to determine current live directory!"); - } - finally - { - File.Delete(RelativePath(LiveDirTest)); - } - - - TheDir = InvertDirectory(TheDir); - } - - //So TheDir is what the Live folder is NOT pointing to - //Now we need to check if DD is running that folder and swap it if necessary - - var rsclock = RelativePath(TheDir + "/" + Config.ProjectName + ".rsc.lk"); - if (File.Exists(rsclock)) - { - try - { - File.Delete(rsclock); - } - catch //held open by byond - { - //This means there is a staged update waiting to be applied, we have to unstage it before we can work - Directory.Delete(RelativePath(GameDirLive)); - CreateSymlink(RelativePath(GameDirLive), RelativePath(TheDir)); - return RelativePath(InvertDirectory(TheDir)); - } - } - return RelativePath(TheDir); - } - - //I hope you can read this - string InvertDirectory(string gameDirectory) - { - if (gameDirectory == GameDirA) - return GameDirB; - else - return GameDirA; - } - - //Compiler thread - void CompileImpl() - { - if(Thread.CurrentThread.Name == null) - Thread.CurrentThread.Name = "Compiler Thread"; - try - { - if (!RepoConfigsMatch()) - { - lock (CompilerLock) - { - lastCompilerError = "Repository TGS3.json does not match cached version! Please update the config appropriately!"; - compilerCurrentStatus = IsInitialized(); - return; - } - } - string resurrectee; - bool repobusy_check = false; - - lock (RepoLock) - { - if (RepoBusy) - repobusy_check = true; - else - RepoBusy = true; - } - - if (repobusy_check) - { - SendMessage("DM: Copy aborted, repo locked!", MessageType.DeveloperInfo); - lock (CompilerLock) - { - lastCompilerError = "The repo could not be locked for copying"; - compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid - return; - } - } - - string CurrentSha; - string dmeName, dmePath; - try - { - bool silent; - lock (CompilerLock) - { - silent = silentCompile; - silentCompile = false; - } - - if (!silent) - SendMessage("DM: Compiling...", MessageType.DeveloperInfo); - - resurrectee = GetStagingDir(); //non-relative - - var Config = GetCachedRepoConfig(); - var deleteExcludeList = new List { BridgeDLLName }; - deleteExcludeList.AddRange(Config.StaticDirectoryPaths); - deleteExcludeList.AddRange(Config.DLLPaths); - Helpers.DeleteDirectory(resurrectee, true, deleteExcludeList); - - - Directory.CreateDirectory(resurrectee + "/.git/logs"); - - foreach (var I in Config.StaticDirectoryPaths) - { - var the_path = Path.Combine(resurrectee, I); - if (!Directory.Exists(the_path)) - CreateSymlink(Path.Combine(resurrectee, I), RelativePath(Path.Combine(StaticDirs, I))); - } - foreach (var I in Config.DLLPaths) - { - var the_path = Path.Combine(resurrectee, I); - if (!File.Exists(the_path)) - CreateSymlink(the_path, RelativePath(Path.Combine(StaticDirs, I))); - } - - if (!File.Exists(Path.Combine(resurrectee, BridgeDLLName))) - CreateSymlink(Path.Combine(resurrectee, BridgeDLLName), RelativePath(BridgeDLLName)); - - deleteExcludeList.Add(".git"); - Helpers.CopyDirectory(RelativePath(RepoPath), resurrectee, deleteExcludeList); - CurrentSha = GetShaOrBranchNoLock(out string error, false, false); - //just the tip - const string GitLogsDir = "/.git/logs"; - Helpers.CopyDirectory(RelativePath(RepoPath + GitLogsDir), resurrectee + GitLogsDir); - dmeName = String.Format(CultureInfo.InvariantCulture, "{0}.dme", ProjectName()); - dmePath = Path.Combine(resurrectee, dmeName); - HandleDMEModifications(resurrectee, dmePath); - try - { - File.Copy(RelativePath(PRJobFile), Path.Combine(resurrectee, PRJobFile)); - } - catch { } - } - finally - { - lock (RepoLock) - RepoBusy = false; - } - - var res = CreateBackup(); - if (res != null) - lock (CompilerLock) - { - lastCompilerError = res; - compilerCurrentStatus = CompilerStatus.Initialized; - return; - } - - if (!File.Exists(dmePath)) - { - var errorMsg = String.Format("Could not find {0}!", dmeName); - SendMessage("DM: " + errorMsg, MessageType.DeveloperInfo); - WriteError(errorMsg, EventID.DMCompileCrash); - lock (CompilerLock) - { - lastCompilerError = errorMsg; - compilerCurrentStatus = CompilerStatus.Initialized; - return; - } - } - - if (!PrecompileHook()) - { - lastCompilerError = "The precompile hook failed"; - compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid - WriteWarning("Precompile hook failed!", EventID.DMCompileError); - return; - } - - bool stagedBuild; - lock (ByondLock) - { - stagedBuild = updateStat == ByondStatus.Staged; - if (stagedBuild) - updateStat = ByondStatus.CompilingStaged; - else if (GetVersion(ByondVersion.Installed) == null) - lock (CompilerLock) - { - lastCompilerError = "BYOND not installed!"; - compilerCurrentStatus = CompilerStatus.Initialized; - return; - } - } - - using (var DM = new Process()) //will kill the process if the thread is terminated - { - DM.StartInfo.FileName = RelativePath(Path.Combine(stagedBuild ? StagingDirectoryInner : ByondDirectory, "bin/dm.exe")); - DM.StartInfo.Arguments = String.Format("-clean {0}", dmePath); - DM.StartInfo.RedirectStandardOutput = true; - DM.StartInfo.UseShellExecute = false; - var OutputList = new StringBuilder(); - DM.OutputDataReceived += new DataReceivedEventHandler( - delegate (object sender, DataReceivedEventArgs e) - { - OutputList.Append(Environment.NewLine); - OutputList.Append(e.Data); - } - ); - try - { - lock (CompilerLock) - { - if (compilationCancellationRequestation) - return; - canCancelCompilation = true; - } - - DM.Start(); - DM.BeginOutputReadLine(); - while (!DM.HasExited) - DM.WaitForExit(100); - DM.CancelOutputRead(); - - lock (CompilerLock) - { - canCancelCompilation = false; - compilationCancellationRequestation = false; - } - } - catch - { - if (!DM.HasExited) - { - DM.Kill(); - DM.WaitForExit(); - } - throw; - } - finally - { - lock (CompilerLock) - { - canCancelCompilation = false; - } - if (stagedBuild) - { - lock (ByondLock) - updateStat = ByondStatus.Staged; - RequestRestart(); - } - } - - if (DM.ExitCode == 0) - { - lock (watchdogLock) - { - //gotta go fast - var online = currentStatus == DreamDaemonStatus.Online; - if (online) - Proc.Suspend(); - try - { - var rgdl = RelativePath(GameDirLive); - if (Directory.Exists(rgdl)) - //these next two lines should be atomic but this is the best we can do - Directory.Delete(rgdl); - CreateSymlink(rgdl, resurrectee); - } - finally - { - if (online && !Proc.HasExited) - Proc.Resume(); - } - } - var staged = DaemonStatus() != DreamDaemonStatus.Offline; - if (!PostcompileHook()) - { - lastCompilerError = "The postcompile hook failed"; - compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid - WriteWarning("Postcompile hook failed!", EventID.DMCompileError); - return; - } - UpdateLiveSha(CurrentSha); - var msg = String.Format("Compile complete!{0}", !staged ? "" : " Server will update on reboot."); - WorldAnnounce("Server updated, changes will be applied on reboot..."); - SendMessage("DM: " + msg, MessageType.DeveloperInfo); - WriteInfo(msg, EventID.DMCompileSuccess); - lock (CompilerLock) - { - if (staged) - UpdateStaged = true; - lastCompilerError = null; - compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid - } - } - else - { - SendMessage("DM: Compile failed!", MessageType.DeveloperInfo); //Also happens for warnings - WriteWarning("Compile error: " + OutputList.ToString(), EventID.DMCompileError); - lock (CompilerLock) - { - lastCompilerError = "DM compile failure"; - compilerCurrentStatus = CompilerStatus.Initialized; - } - } - } - } - catch (ThreadAbortException) - { - return; - } - catch (Exception e) - { - SendMessage("DM: Compiler thread crashed!", MessageType.DeveloperInfo); - WriteError("Compile manager errror: " + e.ToString(), EventID.DMCompileCrash); - lock (CompilerLock) - { - lastCompilerError = e.ToString(); - compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid - } - } - finally - { - lock (CompilerLock) - { - canCancelCompilation = false; - if (compilationCancellationRequestation) - { - compilerCurrentStatus = CompilerStatus.Initialized; - compilationCancellationRequestation = false; - SendMessage("DM: Compile cancelled!", MessageType.DeveloperInfo); - WriteInfo("Compilation cancelled", EventID.DMCompileCancel); - } - } - } - } - //kicks off the compiler thread - /// - public bool Compile(bool silent = false) - { - lock (CompilerLock) - { - if (compilerCurrentStatus != CompilerStatus.Initialized) - return false; - lock (ByondLock) - if (updateStat == ByondStatus.Staged) - return false; - silentCompile = silent; - lastCompilerError = null; - compilerCurrentStatus = CompilerStatus.Compiling; - CompilerThread = new Thread(new ThreadStart(CompileImpl)); - CompilerThread.Start(); - } - return true; - } - - /// - public string ProjectName() - { - lock (CompilerLock) - { - return Config.ProjectName; - } - } - - /// - public void SetProjectName(string projectName) - { - lock (CompilerLock) - { - Config.ProjectName = projectName; - } - } - - public string Cancel() - { - lock (CompilerLock) - { - if (compilerCurrentStatus != CompilerStatus.Compiling) - return "Invalid state for cancellation!"; - compilationCancellationRequestation = true; - if (canCancelCompilation) - CompilerThread.Abort(); - else - return "Compilation will be cancelled when the repo copy is complete"; - return null; - } - } - } -} diff --git a/legacy/V3/TGS.Server/Instance/Config.cs b/legacy/V3/TGS.Server/Instance/Config.cs deleted file mode 100644 index 5eda46466e..0000000000 --- a/legacy/V3/TGS.Server/Instance/Config.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.ServiceModel; -using TGS.Interface.Components; - -namespace TGS.Server -{ - //knobs and such - sealed partial class Instance : ITGConfig - { - /// - /// Used for multithreading safety - /// - object configLock = new object(); //for atomic reads/writes - - /// - public string ReadText(string staticRelativePath, bool repo, out string error, out bool unauthorized) - { - string path = null; - try - { - var configDir = RelativePath(repo ? RepoPath : StaticDirs); - - path = configDir + '/' + staticRelativePath; //do not use path.combine or it will try and take the root - lock (configLock) - { - var di1 = new DirectoryInfo(configDir); - if (repo) - { - //ensure we aren't trying to read anything outside the static dirs - var Config = GetCachedRepoConfig(); - if (Config == null) - { - error = "Unable to load static directory configuration"; - unauthorized = false; - return null; - } - var Found = false; - foreach (var I in Config.StaticDirectoryPaths) - { - if (di1.FullName == new DirectoryInfo(Path.Combine(RelativePath(RepoPath), I)).FullName) - { - Found = true; - break; - } - } - if (!Found) - { - error = "File is not in a configured static directory!"; - unauthorized = false; - return null; - } - } - - var di2 = new DirectoryInfo(new FileInfo(path).Directory.FullName); - - var good = false; - while (di2 != null) - { - if (di2.FullName == di1.FullName) - { - good = true; - break; - } - else di2 = di2.Parent; - } - - if (!good) - { - error = "Cannot read above static directories!"; - unauthorized = false; - return null; - } - - string output; - using (Server.BeginImpersonation()) - output = File.ReadAllText(path); - WriteInfo("Read of " + path, EventID.StaticRead); - error = null; - unauthorized = false; - return output; - } - } - catch (UnauthorizedAccessException e) - { - //no need for the full stacktrace - error = e.Message; - unauthorized = true; - return null; - } - catch (Exception e) - { - error = e.ToString(); - WriteWarning(String.Format("Read of {0} failed! Error: {1}", path, e.ToString()), EventID.StaticRead); - unauthorized = false; - return null; - } - } - - /// - public string WriteText(string staticRelativePath, string data, string originalData, out bool unauthorized) - { - var path = RelativePath(StaticDirs) + '/' + staticRelativePath; //do not use path.combine or it will try and take the root - try - { - lock (configLock) - { - var di1 = new DirectoryInfo(RelativePath(StaticDirs)); - var destdir = new FileInfo(path).Directory.FullName; - var di2 = new DirectoryInfo(destdir); - - var good = false; - while (di2 != null) - { - if (di2.FullName == di1.FullName) - { - good = true; - break; - } - else di2 = di2.Parent; - } - - if (!good) - { - unauthorized = false; - return "Cannot write above static directories!"; - } - - using (Server.BeginImpersonation()) - { - if (originalData != null && File.ReadAllText(path) != originalData) - { - unauthorized = false; - return "File has changed since being initially read. Please refresh!"; - } - Directory.CreateDirectory(destdir); - File.WriteAllText(path, data); - } - WriteInfo("Write to " + path, EventID.StaticWrite); - unauthorized = false; - return null; - } - } - catch (UnauthorizedAccessException e) - { - //no need for the full stacktrace - unauthorized = true; - return e.Message; - } - catch (Exception e) - { - unauthorized = false; - WriteWarning(String.Format("Write of {0} failed! Error: {1}", path, e.ToString()), EventID.StaticRead); - return e.ToString(); - } - } - /// - public string DeleteFile(string staticRelativePath, out bool unauthorized) - { - var path = RelativePath(StaticDirs + '/' + staticRelativePath); //do not use path.combine or it will try and take the root - try - { - lock (configLock) - { - var di1 = new DirectoryInfo(RelativePath(StaticDirs)); - var fi = new FileInfo(path); - var di2 = new DirectoryInfo(fi.Directory.FullName); - - var good = false; - while (di2 != null) - { - if (di2.FullName == di1.FullName) - { - good = true; - break; - } - else di2 = di2.Parent; - } - - if (!good) - { - unauthorized = false; - return "Cannot delete above static directories!"; - } - - using (Server.BeginImpersonation()) - if (fi.Exists) - File.Delete(path); - else if (Directory.Exists(path)) - Helpers.DeleteDirectory(path); - WriteInfo("Delete of " + path, EventID.StaticDelete); - unauthorized = false; - return null; - } - } - catch (UnauthorizedAccessException e) - { - //no need for the full stacktrace - unauthorized = true; - return e.Message; - } - catch (Exception e) - { - unauthorized = false; - WriteWarning(String.Format("Delete of {0} failed! Error: {1}", path, e.ToString()), EventID.StaticRead); - return e.ToString(); - } - } - - /// - public IList ListStaticDirectory(string subDir, out string error, out bool unauthorized) - { - try - { - if (!Directory.Exists(RelativePath(StaticDirs))) - { - error = null; - unauthorized = false; - return new List(); - } - using (Server.BeginImpersonation()) - { - DirectoryInfo dirToEnum = new DirectoryInfo(RelativePath(StaticDirs) + '/' + subDir ?? ""); //do not use path.combine or it will try and take the root - var result = new List(); - foreach (var I in dirToEnum.GetFiles()) - result.Add(I.Name); - foreach (var I in dirToEnum.GetDirectories()) - result.Add('/' + I.Name); - error = null; - unauthorized = false; - return result; - } - } - catch (UnauthorizedAccessException e) - { - //no need for the full stacktrace - error = e.Message; - unauthorized = true; - return null; - } - catch (Exception e) - { - error = e.ToString(); - unauthorized = false; - return null; - } - } - } -} diff --git a/legacy/V3/TGS.Server/Instance/DreamDaemon.cs b/legacy/V3/TGS.Server/Instance/DreamDaemon.cs deleted file mode 100644 index 347514d1c7..0000000000 --- a/legacy/V3/TGS.Server/Instance/DreamDaemon.cs +++ /dev/null @@ -1,768 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Timers; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.Server -{ - //manages the dd window. - //It's not possible to actually click it while starting it in CL mode, so in order to change visibility, security, etc. It restarts the process when the world reboots - sealed partial class Instance : ITGDreamDaemon - { - enum ShutdownRequestPhase - { - None, - Requested, - Pinged, - } - - /// - /// Directory for storing DreamDaemon diagnostic files - /// - const string DiagnosticsDir = "Diagnostics"; - /// - /// Directory for storing DreamDaemon ResourceUsage files - /// - const string ResourceDiagnosticsDir = DiagnosticsDir + "/Resources"; - /// - /// Time until DD is considered DOA on startup - /// - const int DDHangStartTime = 60; - /// - /// If DreamDaemon crashes before this time it is considered a bad startup - /// - const int DDBadStartTime = 10; - - /// - /// The DreamDaemon process - /// - Process Proc; - /// - /// CPU performance information for - /// - PerformanceCounter pcpu; - - /// - /// Used for multithreading safety - /// - object watchdogLock = new object(); - /// - /// The thread that monitors the status of DreamDaemon - /// - Thread DDWatchdog; - /// - /// The of - /// - DreamDaemonStatus currentStatus; - /// - /// Current logfile in use in - /// - string CurrentDDLog; - /// - /// Current port DreamDaemon is running on - /// - ushort currentPort = 0; - - /// - /// Used for multithreading safety - /// - object restartLock = new object(); - /// - /// Used to indicate if an intentional restart is in progress on the watchdog. Requires to access - /// - bool RestartInProgress = false; - /// - /// Used to indicate if an service restart is in progress on the watchdog. Requires to access - /// - bool ReattachInsteadOfRestart = false; - - /// - /// Current level of DreamDaemon - /// - DreamDaemonSecurity StartingSecurity; - - /// - /// Indicator of progress on a or operation - /// - ShutdownRequestPhase AwaitingShutdown; - - /// - /// Setup or reattach the watchdog, depending on of , and create the - /// - void InitDreamDaemon() - { - Directory.CreateDirectory(RelativePath(DiagnosticsDir)); - Directory.CreateDirectory(RelativePath(ResourceDiagnosticsDir)); - var Reattach = Config.ReattachRequired; - if (Reattach) - try - { - Proc = Process.GetProcessById(Config.ReattachProcessID); - if (Proc == null) - throw new Exception("GetProcessById returned null!"); - WriteInfo("Reattached to running DD process!", EventID.DDReattachSuccess); - ThreadPool.QueueUserWorkItem(_ => - { - Thread.Sleep(5000); - SendMessage("DD: Update complete. Watch dog reactivated...", MessageType.WatchdogInfo); - }); - - //start wd - lock (restartLock) - { - RestartInProgress = true; - ReattachInsteadOfRestart = true; - } - currentPort = Config.ReattachPort; - serviceCommsKey = Config.ReattachCommsKey; - try - { - GameAPIVersion = new Version(Config.ReattachAPIVersion); - } - catch { } - currentStatus = DreamDaemonStatus.Online; - DDWatchdog = new Thread(new ThreadStart(Watchdog)); - DDWatchdog.Start(); - } - catch (Exception e) - { - WriteError(String.Format("Failed to reattach to DreamDaemon! PID: {0}. Exception: {1}", Config.ReattachProcessID, e.ToString()), EventID.DDReattachFail); - } - finally - { - Config.ReattachRequired = false; - Config.Save(); - } - - if (Proc == null) - Proc = new Process(); - - Proc.StartInfo.FileName = RelativePath(ByondDirectory + "/bin/dreamdaemon.exe"); - Proc.StartInfo.UseShellExecute = false; - - if (Reattach) - return; - - //autostart the server - if (Config.Autostart) - //break this off so we don't hold up starting the service - ThreadPool.QueueUserWorkItem(_ => { Start(); }); - } - - /// - /// Either let go of for reattachment or terminate it, depending on of - /// - void DisposeDreamDaemon() - { - var Detach = Config.ReattachRequired; - bool RenameLog = false; - if (DaemonStatus() == DreamDaemonStatus.Online) - { - if (!Detach) - { - WorldAnnounce("Server service stopped"); - Thread.Sleep(1000); - } - else - { - RenameLog = CurrentDDLog != null; - SendMessage("DD: Detaching watch dog for update!", MessageType.WatchdogInfo); - WriteCurrentDDLog("Service updating! Splitting diagnostics..."); - } - } - else if (Detach) - Config.ReattachRequired = false; - Stop(); - if (pcpu != null) - pcpu.Dispose(); - if (RenameLog) - try - { - var rrdd = RelativePath(ResourceDiagnosticsDir); - File.Move(Path.Combine(rrdd, CurrentDDLog), Path.Combine(rrdd, "SU-" + CurrentDDLog)); - } - catch { } - } - - /// - public DreamDaemonStatus DaemonStatus() - { - lock (watchdogLock) - { - return currentStatus; - } - } - - /// - public void RequestRestart() - { - SendCommand(SCHardReboot); - } - - /// - public void RequestStop() - { - lock (watchdogLock) - { - if (currentStatus != DreamDaemonStatus.Online || AwaitingShutdown != ShutdownRequestPhase.None) - return; - AwaitingShutdown = ShutdownRequestPhase.Pinged; - } - SendCommand(SCGracefulShutdown); - } - - /// - public string Stop() - { - Thread t; - lock (watchdogLock) - { - t = DDWatchdog; - DDWatchdog = null; - } - if (t != null && t.IsAlive) - { - t.Abort(); - t.Join(); - return null; - } - else - return "Server not running"; - } - - /// - public void SetPort(ushort new_port) - { - lock (watchdogLock) - { - Config.Port = new_port; - RequestRestart(); - } - } - - //handle a kill request from the server - public void KillMe(bool silent) - { - bool DoRestart; - lock (watchdogLock) - { - DoRestart = AwaitingShutdown == ShutdownRequestPhase.None; - if (!DoRestart) - AwaitingShutdown = ShutdownRequestPhase.Pinged; - } - //Do this is a seperate thread or we'll kill this thread in the middle of rebooting - if (DoRestart) - ThreadPool.QueueUserWorkItem(_ => { RestartImpl(silent); }); - else - ThreadPool.QueueUserWorkItem(_ => { Stop(); }); - } - - /// - public string Restart() - { - if (DaemonStatus() == DreamDaemonStatus.Offline) - return Start(); - return RestartImpl(false); - } - - /// - /// Restarts DD - /// - /// If no chat message will be sent - /// on success, error message on failure - string RestartImpl(bool silent) - { - lock (restartLock) - { - if (RestartInProgress) - return "Restart already in progress"; - RestartInProgress = true; - } - if (!silent) - SendMessage("DD: Hard restart triggered", MessageType.WatchdogInfo); - Stop(); - var res = Start(); - if (res != null) - lock (restartLock) - { - RestartInProgress = false; - } - return res; - } - - /// - /// Write a to the . A timestamp will be prepended to it - /// - /// The message to log - void WriteCurrentDDLog(string message) - { - lock (watchdogLock) - { - if (currentStatus != DreamDaemonStatus.Online || CurrentDDLog == null) - return; - File.AppendAllText(Path.Combine(RelativePath(ResourceDiagnosticsDir), CurrentDDLog), String.Format("[{0}]: {1}\n", DateTime.Now.ToLongTimeString(), message)); - } - } - - /// - /// Threaded loop that keeps DreamDaemon from unintentionally stopping - /// - void Watchdog() - { - Thread.CurrentThread.Name = "Watchdog Thread"; - try - { - lock (restartLock) - { - if (!RestartInProgress) - { - SendMessage("DD: Server started, watchdog active...", MessageType.WatchdogInfo); - WriteInfo("Watchdog started", EventID.DDWatchdogStarted); - } - else - { - RestartInProgress = false; - if (!ReattachInsteadOfRestart) - WriteInfo("Watchdog restarted", EventID.DDWatchdogRestarted); - else - ReattachInsteadOfRestart = false; - } - } - var retries = 0; - - var MemTrackTimer = new System.Timers.Timer - { - AutoReset = true, - Interval = 5000 //every 5 seconds - }; - MemTrackTimer.Elapsed += MemTrackTimer_Elapsed; - while (true) - { - var starttime = DateTime.Now; - - lock (watchdogLock) - { - if (AwaitingShutdown == ShutdownRequestPhase.Requested) - SendCommand(SCGracefulShutdown); - } - - //all good to go, let's start monitoring - var Now = DateTime.Now; - lock (watchdogLock) - { - CurrentDDLog = DateTime.UtcNow.ToString("yyyy-MM-ddTHH-mm-ssZ"); - WriteCurrentDDLog("Starting monitoring..."); - } - try - { - pcpu = new PerformanceCounter("Process", "% Processor Time", Proc.ProcessName, true); - MemTrackTimer.Start(); - } - catch (InvalidOperationException) { /*process already exited*/ } - try - { - Proc.WaitForExit(); - } - finally - { - lock (watchdogLock) //synchronize - { - MemTrackTimer.Stop(); - pcpu?.Dispose(); - } - } - - WriteCurrentDDLog("Crash detected!"); - - lock (watchdogLock) - { - currentStatus = DreamDaemonStatus.HardRebooting; - currentPort = 0; - Proc.Close(); - - if (AwaitingShutdown == ShutdownRequestPhase.Pinged) - return; - var BadStart = (DateTime.Now - starttime).TotalSeconds < DDBadStartTime; - if (BadStart) - { - ++retries; - var sleep_time = (int)Math.Min(Math.Pow(2, retries), 3600); //max of one hour - SendMessage(String.Format("DD: Watchdog server startup failed! Retrying in {0} seconds...", sleep_time), MessageType.WatchdogInfo); - Thread.Sleep(sleep_time * 1000); - } - else - { - retries = 0; - var msg = "DD: DreamDaemon crashed! Watchdog rebooting DD..."; - SendMessage(msg, MessageType.WatchdogInfo); - WriteWarning(msg, EventID.DDWatchdogRebootingServer); - } - } - - var res = StartImpl(true); - if (res != null) - throw new Exception("Hard restart failed: " + res); - } - } - catch (ThreadAbortException) - { - //No Mr bond, I expect you to die - try - { - if (!Config.ReattachRequired) - { - Proc.Kill(); - Proc.WaitForExit(); - } - else - { - Config.ReattachProcessID = Proc.Id; - Config.ReattachPort = currentPort; - Config.ReattachAPIVersion = GameAPIVersion != null ? GameAPIVersion.ToString() : null; - Config.ReattachCommsKey = serviceCommsKey; - lock (restartLock) - { - RestartInProgress = true; - } - } - Proc.Close(); - } - catch - { } - } - catch (Exception e) - { - SendMessage("DD: Watchdog thread crashed!", MessageType.WatchdogInfo); - WriteError("Watch dog thread crashed: " + e.ToString(), EventID.DDWatchdogCrash); - } - finally - { - lock (watchdogLock) - { - currentStatus = DreamDaemonStatus.Offline; - currentPort = 0; - AwaitingShutdown = ShutdownRequestPhase.None; - lock (restartLock) - { - if (!RestartInProgress) - { - if (!Config.ReattachRequired) - SendMessage("DD: Server stopped, watchdog exiting...", MessageType.WatchdogInfo); - WriteInfo("Watch dog exited", EventID.DDWatchdogExit); - } - else - WriteInfo("Watch dog restarting...", EventID.DDWatchdogRestart); - } - } - } - } - - /// - /// Called every five seconds while DreamDaemon is running to log it's current state to the - /// - /// The event sender, an instance of - /// The - private void MemTrackTimer_Elapsed(object sender, ElapsedEventArgs e) - { - ulong megamem; - float cputime; - lock (watchdogLock) - { - cputime = pcpu.NextValue(); - using (var pcm = new PerformanceCounter("Process", "Working Set - Private", Proc.ProcessName, true)) - megamem = Convert.ToUInt64(pcm.NextValue()) / 1024; - } - var PercentCpuTime = (int)Math.Round((Decimal)cputime); - WriteCurrentDDLog(String.Format("CPU: {1}% Memory: {0}KB", megamem, PercentCpuTime.ToString("D3"))); - } - - /// - public string CanStart() - { - if (GetVersion(ByondVersion.Installed) == null) - return "Byond is not installed!"; - var DMB = RelativePath(GameDirLive + "/" + Config.ProjectName + ".dmb"); - if (!File.Exists(DMB)) - return String.Format("Unable to find {0}!", DMB); - return null; - } - - /// - public string Start() - { - if (CurrentStatus() == ByondStatus.Staged) - { - //IMPORTANT: SLEEP FOR A MOMENT OR WONDOWS WON'T RELEASE THE FUCKING BYOND DLL HANDLES!!!! REEEEEEE - Thread.Sleep(3000); - ApplyStagedUpdate(); - } - lock (watchdogLock) - { - if (currentStatus != DreamDaemonStatus.Offline) - return "Server already running"; - var res = CanStart(); - if (res != null) - return res; - currentPort = 0; - currentStatus = DreamDaemonStatus.HardRebooting; - } - return StartImpl(false); - } - - /// - /// Translate the configured level into a byond command line param - /// - /// If bases it's result on , uses of otherwise - /// "safe", "trusted", or "ultrasafe" depending on the it checks - string SecurityWord(bool starting = false) - { - var level = starting ? StartingSecurity : Config.Security; - switch (level) - { - case DreamDaemonSecurity.Safe: - return "safe"; - case DreamDaemonSecurity.Trusted: - return "trusted"; - case DreamDaemonSecurity.Ultrasafe: - return "ultrasafe"; - default: - throw new Exception(String.Format("Bad DreamDaemon security level: {0}", level)); - } - } - - /// - /// Copies from the program directory to the the directory - /// - /// If , overwrites the 's current interface .dll if it exists - void UpdateBridgeDll(bool overwrite) - { - var rbdlln = RelativePath(BridgeDLLName); - var FileExists = File.Exists(rbdlln); - if (FileExists && !overwrite) - return; - //Copy the interface dll to the static dir - - var InterfacePath = Assembly.GetAssembly(typeof(IClient)).Location; - //bridge is installed next to the interface - var BridgePath = Path.Combine(Path.GetDirectoryName(InterfacePath), BridgeDLLName); -#if DEBUG - //We could be debugging from the project directory - if (!File.Exists(BridgePath)) - //A little hackish debug mode doctoring never hurt anyone - BridgePath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(InterfacePath)))), "TGS.Interface.Bridge/bin/x86/Debug", BridgeDLLName); -#endif - try - { - //Use reflection to ensure these are the droids we're looking for - Assembly.ReflectionOnlyLoadFrom(BridgePath).GetType(String.Format("{0}.{1}.{2}.{3}", nameof(TGS), nameof(Interface), DreamDaemonBridgeNamespace, DreamDaemonBridgeType), true); - } - catch (Exception e) - { - WriteError(String.Format("Unable to locate {0}! Error: {1}", BridgeDLLName, e.ToString()), EventID.BridgeDLLUpdateFail); - return; - } - - try - { - if (FileExists) - { - var Old = File.ReadAllBytes(rbdlln); - var New = File.ReadAllBytes(BridgePath); - if (Old.SequenceEqual(New)) - return; //no need - } - File.Copy(BridgePath, rbdlln, overwrite); - } - catch - { - try - { - //ok the things being stupid and hasn't released the dll yet, try ONCE more - Thread.Sleep(1000); - File.Copy(BridgePath, rbdlln, overwrite); - } - catch (Exception e) - { - //intentionally using the fi - WriteError("Failed to update bridge DLL! Error: " + e.ToString(), EventID.BridgeDLLUpdateFail); - return; - } - } - WriteInfo("Updated interface DLL", EventID.BridgeDLLUpdated); - } - - /// - /// Clears the current , calls with a parameter, and attempts to start the DreamDaemon - /// - /// If , sets to a new pointing to and starts it - /// on success, error message on failure - string StartImpl(bool watchdog) - { - try - { - lock (watchdogLock) - { - var res = CanStart(); - if (res != null) - return res; - - var DMB = RelativePath(GameDirLive + "/" + Config.ProjectName + ".dmb"); - - GenCommsKey(); - StartingSecurity = Config.Security; - Proc.StartInfo.Arguments = String.Format("{0} -port {1} {5}-close -verbose -params \"server_service={3}&server_service_version={4}&{6}={7}\" -{2} -public", DMB, Config.Port, SecurityWord(), serviceCommsKey, Version(), Config.Webclient ? "-webclient " : "", SPInstanceName, Config.Name); - UpdateBridgeDll(true); - ClearCacheFolder(); - lock (topicLock) - { - GameAPIVersion = null; //needs updating - } - Proc.Start(); - Proc.PriorityClass = ProcessPriorityClass.AboveNormal; - - if (!Proc.WaitForInputIdle(DDHangStartTime * 1000)) - { - Proc.Kill(); - Proc.WaitForExit(); - Proc.Close(); - currentStatus = DreamDaemonStatus.Offline; - currentPort = 0; - return String.Format("Server start is taking more than {0}s! Aborting!", DDHangStartTime); - } - currentPort = Config.Port; - currentStatus = DreamDaemonStatus.Online; - if (!watchdog) - { - DDWatchdog = new Thread(new ThreadStart(Watchdog)); - DDWatchdog.Start(); - } - return null; - } - } - catch (Exception e) - { - currentStatus = DreamDaemonStatus.Offline; - return e.ToString(); - } - } - - /// - public DreamDaemonSecurity SecurityLevel() - { - lock (watchdogLock) - { - return Config.Security; - } - } - - /// - public bool SetSecurityLevel(DreamDaemonSecurity level) - { - bool needReboot; - lock (watchdogLock) - { - needReboot = Config.Security != level; - Config.Security = level; - } - if (needReboot) - RequestRestart(); - return DaemonStatus() != DreamDaemonStatus.Online; - } - - /// - public bool Autostart() - { - return Config.Autostart; - } - - /// - public void SetAutostart(bool on) - { - Config.Autostart = on; - } - - /// - public string StatusString(bool includeMetaInfo) - { - const string visSecStr = " (Sec: {0})"; - string res; - var ds = DaemonStatus(); - switch (ds) - { - case DreamDaemonStatus.Offline: - res = "OFFLINE"; - break; - case DreamDaemonStatus.HardRebooting: - res = "REBOOTING"; - break; - case DreamDaemonStatus.Online: - res = "ONLINE"; - if (includeMetaInfo) - { - string secandvis; - lock (watchdogLock) - { - secandvis = String.Format(visSecStr, SecurityWord(true)); - } - res += secandvis; - } - break; - default: - res = "NULL AND ERRORS"; - break; - } - if (includeMetaInfo && ds != DreamDaemonStatus.Online) - res += String.Format(visSecStr, SecurityWord()); - return res; - } - - /// - public ushort Port() - { - return Config.Port; - } - - /// - public bool ShutdownInProgress() - { - lock (watchdogLock) - { - return AwaitingShutdown != ShutdownRequestPhase.None; - } - } - - /// - public string WorldAnnounce(string message) - { - var res = SendCommand(SCWorldAnnounce + ";message=" + Helpers.SanitizeTopicString(message)); - if (res == "SUCCESS") - return null; - return res; - } - - /// - public bool Webclient() - { - return Config.Webclient; - } - - /// - public void SetWebclient(bool on) - { - lock (watchdogLock) - { - var diff = on != Config.Webclient; - if (diff) - { - Config.Webclient = on; - RequestRestart(); - } - } - } - } -} diff --git a/legacy/V3/TGS.Server/Instance/Instance.cs b/legacy/V3/TGS.Server/Instance/Instance.cs deleted file mode 100644 index e28d58dc72..0000000000 --- a/legacy/V3/TGS.Server/Instance/Instance.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.ServiceModel; -using TGS.Interface.Components; - -namespace TGS.Server -{ - //I know the fact that this is one massive partial class is gonna trigger everyone - //There really was no other succinct way to do it (<= He's lying through his teeth, don't listen to him) - - //this line basically says take one instance of the service, use it multithreaded for requests, and never delete it - - /// - /// The class which holds all interface components. There are no safeguards for call race conditions so these must be guarded against internally - /// - [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)] - sealed partial class Instance : IDisposable, ITGConnectivity, ITGInstance - { - /// - /// Used to assign the instance to event IDs - /// - public readonly byte LoggingID; - /// - /// The configuration settings for the instance - /// - readonly IInstanceConfig Config; - /// - /// Constructs and a - /// - public Instance(IInstanceConfig config, byte logID) - { - LoggingID = logID; - Config = config; - InitAdministration(); - InitEventHandlers(); - InitChat(); - InitRepo(); - InitByond(); - InitDreamDaemon(); - InitCompiler(); - } - - /// - /// Cleans up the - /// - void RunDisposals() - { - DisposeDreamDaemon(); - DisposeCompiler(); - DisposeByond(); - DisposeRepo(); - DisposeChat(); - DisposeAdministration(); - Config.Save(); - } - - /// - /// Writes information to the Windows event log - /// - /// The log message - /// The of the message - void WriteInfo(string message, EventID id) - { - Server.Logger.WriteInfo(message, id, LoggingID); - } - - /// - /// Writes an error to the Windows event log - /// - /// The log message - /// The of the message - void WriteError(string message, EventID id) - { - Server.Logger.WriteError(message, id, LoggingID); - } - - /// - /// Writes a warning to the Windows event log - /// - /// The log message - /// The of the message - void WriteWarning(string message, EventID id) - { - Server.Logger.WriteWarning(message, id, LoggingID); - } - - /// - /// Writes an access event to the Windows event log - /// - /// The (un)authenticated Windows user's name - /// if authenticated sucessfully, otherwise - void WriteAccess(string username, bool authSuccess) - { - Server.Logger.WriteAccess(String.Format("Access from: {0}", username), authSuccess, LoggingID); - } - - /// - /// Converts relative paths to full directory paths - /// - /// - string RelativePath(string path) - { - return Path.Combine(Config.Directory, path); - } - - /// - public string Version() - { - return Server.VersionString; - } - - /// - public void VerifyConnection() { } - - /// - public void Reattach(bool silent) - { - Config.ReattachRequired = true; - if(!silent) - SendMessage("SERVICE: Update started...", MessageType.DeveloperInfo); - } - - /// - public string ServerDirectory() - { - return Config.Directory; - } - - /// - /// Sets of to - /// - public void Offline() - { - Config.Enabled = false; - } - - //mostly generated code with a call to RunDisposals() - //you don't need to open this - #region IDisposable Support - /// - /// To detect redundant calls - /// - private bool disposedValue = false; - - /// - /// Implements the pattern. Calls - /// - /// if was called manually, if it was from the finalizer - void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - RunDisposals(); - // TODO: dispose managed state (managed objects). - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~TGStationServer() { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - /// - /// Implements the pattern - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - } -} diff --git a/legacy/V3/TGS.Server/Instance/Interop.cs b/legacy/V3/TGS.Server/Instance/Interop.cs deleted file mode 100644 index 4e4aa22cd7..0000000000 --- a/legacy/V3/TGS.Server/Instance/Interop.cs +++ /dev/null @@ -1,281 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Web.Security; -using TGS.Server.ChatCommands; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.Server -{ - //handles talking between the world and us - sealed partial class Instance : ITGInterop - { - - object topicLock = new object(); - const int CommsKeyLen = 64; - string serviceCommsKey; //regenerated every DD restart - - //range of supported api versions - const int AllowedMajorAPIVersion = 2; - Version GameAPIVersion; - - const string SPInstanceName = "server_instance"; - - //See code/modules/server_tools/server_tools.dm for command switch - const string SCHardReboot = "hard_reboot"; //requests that dreamdaemon restarts when the round ends - const string SCGracefulShutdown = "graceful_shutdown"; //requests that dreamdaemon stops when the round ends - const string SCWorldAnnounce = "world_announce"; //sends param 'message' to the world - const string SCListCustomCommands = "list_custom_commands"; //Get a list of commands supported by the server - const string SCAPICompat = "api_compat"; //Tells the server we understand each other - const string SCPlayerCount = "client_count"; //Gets the number of connected client - - /// - /// String returned when a command completes successfully with no output - /// - const string SRetSuccess = "SUCCESS"; - - const string SRKillProcess = "killme"; - const string SRKillProcessSilent = "killmesilent"; - const string SRIRCBroadcast = "irc"; - const string SRIRCAdminChannelMessage = "send2irc"; - const string SRWorldReboot = "worldreboot"; - const string SRAPIVersion = "api_ver"; - - const string CCPHelpText = "help_text"; - const string CCPAdminOnly = "admin_only"; - const string CCPRequiredParameters = "required_parameters"; - - /// - /// The file name of the .dll that contains the bridge class - /// - const string BridgeDLLName = "TGDreamDaemonBridge.dll"; - /// - /// The namespace that contains the bridge class. Used for reflection - /// - const string DreamDaemonBridgeNamespace = "Bridge"; - /// - /// The bridge class. Used for reflection - /// - const string DreamDaemonBridgeType = "DreamDaemonBridge"; - - List ServerChatCommands; - - void LoadServerChatCommands() - { - if (DaemonStatus() != DreamDaemonStatus.Online) - return; - var json = SendCommand(SCListCustomCommands); - if (String.IsNullOrWhiteSpace(json)) - return; - List tmp = new List(); - try - { - foreach (var I in JsonConvert.DeserializeObject>(json)) - { - var innerDick = ((JObject)I.Value).ToObject>(); - var helpText = (string)innerDick[CCPHelpText]; - var adminOnly = ((long)innerDick[CCPAdminOnly]) == 1; - var requiredParams = (int)((long)innerDick[CCPRequiredParameters]); - tmp.Add(new ServerChatCommand(I.Key, helpText, adminOnly, requiredParams)); - } - ServerChatCommands = tmp; - } - catch { } - } - - //raw command string sent here via world.ExportService - void HandleCommand(string cmd) - { - var splits = new List(cmd.Split(' ')); - cmd = splits[0]; - splits.RemoveAt(0); - - bool APIValid; - lock (topicLock) - { - APIValid = CheckAPIVersionConstraints(); - } - - if (!APIValid && cmd != SRAPIVersion) - return; //SPEAK THE LANGUAGE!!! - - var showKillMessage = true; - switch (cmd) - { - case SRIRCBroadcast: - SendMessage("GAME: " + String.Join(" ", splits), MessageType.GameInfo); - break; - case SRKillProcessSilent: - showKillMessage = false; - goto case SRKillProcess; - case SRKillProcess: - KillMe(!showKillMessage); - break; - case SRIRCAdminChannelMessage: - SendMessage("RELAY: " + String.Join(" ", splits), MessageType.AdminInfo); - break; - case SRWorldReboot: - WriteInfo("World Rebooted", EventID.WorldReboot); - WriteCurrentDDLog("World rebooted"); - ServerChatCommands = null; - ChatConnectivityCheck(); - lock (CompilerLock) - { - if (UpdateStaged) - { - UpdateStaged = false; - lock (topicLock) - { - GameAPIVersion = null; //needs updating - } - WriteInfo("Staged update applied", EventID.ServerUpdateApplied); - } - } - break; - case SRAPIVersion: - lock (topicLock) - { - try - { - GameAPIVersion = new Version(splits[0]); - if (!CheckAPIVersionConstraints()) - throw new Exception(); - } - catch - { - WriteWarning(String.Format("API version of the game ({0}) is incompatible with the current supported API versions (3.{2}.x.x). Interop disabled.", splits.Count > 1 ? splits[1] : "NULL", AllowedMajorAPIVersion), EventID.APIVersionMismatch); - GameAPIVersion = null; - break; - } - } - //This needs to be done asyncronously otherwise DD won't be able to process it, because it's waiting for THIS THREAD to return - ThreadPool.QueueUserWorkItem(_ => SendCommand(SCAPICompat)); - break; - } - } - - public string SendCommand(string cmd) - { - lock (watchdogLock) - { - if (currentStatus != DreamDaemonStatus.Online) - return "Error: Server Offline!"; - return SendTopic(String.Format("serviceCommsKey={0};command={1}", serviceCommsKey, cmd), currentPort); - } - } - - public int PlayerCount() - { - try - { - return Convert.ToInt32(SendCommand(SCPlayerCount)); - } - catch - { - return -1; - } - } - - //requires topiclock - bool CheckAPIVersionConstraints() - { - //major will never change for all of TGS3 - //we treat minor as major, build as minor, and revision as patch - return !(GameAPIVersion == null || GameAPIVersion.Minor != AllowedMajorAPIVersion); - } - - //Fuckery to diddle byond with the right packet to accept our girth - string SendTopic(string topicdata, ushort port) - { - //santize the escape characters in accordance with http://www.byond.com/docs/ref/info.html#/proc/params2list - lock (topicLock) { - if (!CheckAPIVersionConstraints()) - return "Incompatible API!"; - - StringBuilder stringPacket = new StringBuilder(); - stringPacket.Append((char)'\x00', 8); - stringPacket.Append('?' + topicdata); - stringPacket.Append((char)'\x00'); - string fullString = stringPacket.ToString(); - var packet = Encoding.ASCII.GetBytes(fullString); - packet[1] = 0x83; - var FinalLength = packet.Length - 4; - if (FinalLength > UInt16.MaxValue) - return "Error: Topic too long"; - - var lengthBytes = BitConverter.GetBytes((ushort)FinalLength); - - packet[2] = lengthBytes[1]; //fucking endianess - packet[3] = lengthBytes[0]; - - var returnedString = "NULL"; - var returnedData = new byte[UInt16.MaxValue]; - using (var topicSender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { SendTimeout = 5000, ReceiveTimeout = 10000 }) - try - { - topicSender.Connect(IPAddress.Loopback, port); - topicSender.Send(packet); - - try - { - topicSender.Receive(returnedData); - var raw_string = Encoding.ASCII.GetString(returnedData).TrimEnd(new char[] { (char)0 }).Trim(); - if (raw_string.Length > 6) - returnedString = raw_string.Substring(5, raw_string.Length - 5).Trim(); - } - catch - { - returnedString = "Topic recieve error!"; - } - finally - { - topicSender.Shutdown(SocketShutdown.Both); - } - } - catch - { - return "Topic delivery failed!"; - } - - return returnedString; - } - } - - //Every time we make a new DD process we generate a new comms key for security - //It's in world.params['server_service'] - void GenCommsKey() - { - var charsToRemove = new string[] { "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", "=", "[", "{", "]", "}", ";", ":", "<", ">", "|", ".", "/", "?" }; - serviceCommsKey = String.Empty; - do { - var tmp = Membership.GeneratePassword(CommsKeyLen, 0); - foreach (var c in charsToRemove) - tmp = tmp.Replace(c, String.Empty); - serviceCommsKey += tmp; - } while (serviceCommsKey.Length < CommsKeyLen); - serviceCommsKey = serviceCommsKey.Substring(0, CommsKeyLen); - WriteInfo("Service Comms Key set to: " + serviceCommsKey, EventID.CommsKeySet); - } - - /// - public bool InteropMessage(string command) - { - try - { - HandleCommand(command); - return true; - } - catch(Exception e) - { - WriteWarning(String.Format("Handle command for \"{0}\" failed: {1}", command, e.ToString()), EventID.InteropCallException); - return false; - } - } - } -} diff --git a/legacy/V3/TGS.Server/Instance/PreactionHandler.cs b/legacy/V3/TGS.Server/Instance/PreactionHandler.cs deleted file mode 100644 index 8fc1d14792..0000000000 --- a/legacy/V3/TGS.Server/Instance/PreactionHandler.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; - -namespace TGS.Server -{ - // Some useful functions for triggering pre action events - sealed partial class Instance - { - /// - /// The instance directory for Preaction handlers - /// - const string EventFolder = "EventHandlers/"; - - /// - /// Creates the - /// - void InitEventHandlers() - { - Directory.CreateDirectory(RelativePath(EventFolder)); - } - - /// - /// Gets the path of an event given an - /// - /// The name of the event - /// The path to the event handler - string GetEventPath(string eventName) - { - return string.Format("{0}{1}.bat", RelativePath(EventFolder), eventName); - } - - /// - /// Check if an event handler for exists - /// - /// The name of the event - /// if the event handler exists, otherwise - bool EventHandlerExists(string eventName) - { - return File.Exists(GetEventPath(eventName)); - } - - /// - /// Runs an event named if it exists - /// - /// The name of the event - /// if the event handler exists and failed to run, otherwise - bool HandleEvent(string eventName) - { - - if (!EventHandlerExists(eventName)) - { - // We don't need a handler, so let's just fail silently. - return true; - } - - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = GetEventPath(eventName), - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - } - }; - process.Start(); - process.WaitForExit(); - - var stdout = process.StandardOutput.ReadToEnd(); - var stderr = process.StandardError.ReadToEnd(); - var success = process.ExitCode == 0; - var eventData = String.Format("Preaction Event: {0} @ {1} ran. Stdout:\n{2}\nStderr:\n{3}", eventName, GetEventPath(eventName), stdout, stderr); - - if (success) - WriteInfo(eventData, EventID.PreactionEvent); - else - WriteWarning(eventData, EventID.PreactionFail); - - return success; - } - - /// - /// Run the "precompile" event - /// - /// if the event handler exists and failed to run, otherwise - public bool PrecompileHook() - { - return HandleEvent("precompile"); - } - - /// - /// Run the "postcompile" event - /// - /// if the event handler exists and failed to run, otherwise - public bool PostcompileHook() - { - return HandleEvent("postcompile"); - } - } -} diff --git a/legacy/V3/TGS.Server/Instance/Repository.cs b/legacy/V3/TGS.Server/Instance/Repository.cs deleted file mode 100644 index 0a7a7a60f9..0000000000 --- a/legacy/V3/TGS.Server/Instance/Repository.cs +++ /dev/null @@ -1,1464 +0,0 @@ -using LibGit2Sharp; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.Server -{ - sealed partial class Instance : ITGRepository, IDisposable - { - /// - /// The directory for the repository - /// - const string RepoPath = "Repository"; - /// - /// The branch name used for publishing testmerge commits - /// - const string RemoteTempBranchName = "___TGS3TempBranch"; - /// - /// The path to the Repository's json - /// - const string RepoTGS3SettingsPath = RepoPath + "/TGS3.json"; - /// - /// Path to the 's json - /// - const string CachedTGS3SettingsPath = "TGS3.json"; - /// - /// Error message for when a merge operation fails due to the target branch already having the source branch's commits - /// - const string RepoErrorUpToDate = "Already up to date!"; - /// - /// Git remote for push operations - /// - const string SSHPushRemote = "ssh_push_target"; - /// - /// The directory for the repository SSH keys - /// - const string RepoKeyDir = "RepoKey/"; - /// - /// The path to the private ssh-rsa key file - /// - const string PrivateKeyPath = RepoKeyDir + "private_key.txt"; - /// - /// The path to the public ssh-rsa key file - /// - const string PublicKeyPath = RepoKeyDir + "public_key.txt"; - /// - /// File name for monitoring which github pull requests are currently test merged - /// - const string PRJobFile = "prtestjob.json"; - /// - /// The branch that points to the current commit that is live or staged to be live in DreamDaemon - /// - const string LiveTrackingBranch = "___TGSLiveCommitTrackingBranch"; - /// - /// Commit message for - /// - const string CommitMessage = "Automatic changelog compile, [ci skip]"; - - /// - /// Used in conjunction with for multithreading safety - /// - object RepoLock = new object(); - /// - /// Used for multithreading safety. You may not use the repo if this is not locked or . Either old the lock while you do work or set to then release it, do your work, reaquire it, and set back to - /// - bool RepoBusy = false; - /// - /// Whether or not a git operation is in progress. See - /// - bool Cloning = false; - - /// - /// The repository object - /// - Repository Repo; - /// - /// Used for reporting operation progress to the - /// - int currentProgress = -1; - - /// - /// Used for automatically updating the - /// - System.Timers.Timer autoUpdateTimer = new System.Timers.Timer() - { - AutoReset = true - }; - - /// - /// Initializes the repository - /// - void InitRepo() - { - Directory.CreateDirectory(RelativePath(RepoKeyDir)); - if (Exists()) - UpdateBridgeDll(false); - if (LoadRepo() == null) - DisableGarbageCollectionNoLock(); - //start the autoupdate timer - autoUpdateTimer.Elapsed += AutoUpdateTimer_Elapsed; - SetAutoUpdateInterval(Config.AutoUpdateInterval); - } - - /// - /// Checks if the and json files match. - /// - /// if the jsons match, otherwise - bool RepoConfigsMatch() - { - //this should never be called while the repo is busy - RepoConfig I = null; - lock (RepoLock) - if (!RepoBusy && LoadRepo() == null) - I = new RepoConfig(RelativePath(RepoTGS3SettingsPath)); - if (I == null) - throw new Exception("Unable to load TGS3.json from repo!"); - var J = GetCachedRepoConfig(); - return I == J; - } - - /// - /// Gets the for - /// - /// The for - RepoConfig GetCachedRepoConfig() - { - return new RepoConfig(RelativePath(CachedTGS3SettingsPath)); - } - - /// - public bool OperationInProgress() - { - lock (RepoLock) - return RepoBusy; - } - - /// - public int CheckoutProgress() - { - return currentProgress; - } - - /// - /// Initializes - /// - /// on success, error message on failure - string LoadRepo() - { - if (Repo != null) - return null; - if (!Repository.IsValid(RelativePath(RepoPath))) - return "Repository does not exist"; - try - { - Repo = new Repository(RelativePath(RepoPath)); - } - catch (Exception e) - { - return e.ToString(); - } - return null; - } - - /// - /// Cleans up - /// - void DisposeRepo() - { - if (Repo != null) - { - Repo.Dispose(); - Repo = null; - } - } - - /// - public bool Exists() - { - lock (RepoLock) - return !Cloning && Repository.IsValid(RelativePath(RepoPath)); - } - - /// - /// Updates with the progess of the current transfer operation - /// - /// The of the current transfer operation - /// - bool HandleTransferProgress(TransferProgress progress) - { - currentProgress = ((int)(((float)progress.ReceivedObjects / progress.TotalObjects) * 100) / 2) + ((int)(((float)progress.IndexedObjects / progress.TotalObjects) * 100) / 2); - return true; - } - - /// - /// Updates with the progess of the current checkout operation - /// - /// Ignored - /// Dividend for progress calculation - /// Divisor for progress calculation - void HandleCheckoutProgress(string path, int completedSteps, int totalSteps) - { - currentProgress = (int)(((float)completedSteps / totalSteps) * 100); - } - - /// - /// Backups up the and deletes the current if they exist. Clones the given branch of the given remote. Sets up new - /// - /// Remote and branch name, seperated by a ' ' - void Clone(object twostrings) - { - //busy flag set by caller - var ts = ((string)twostrings).Split(' '); - var RepoURL = ts[0]; - var BranchName = ts[1]; - try - { - SendMessage(String.Format("REPO: {2} started: Cloning {0} branch of {1} ...", BranchName, RepoURL, Repository.IsValid(RepoPath) ? "Full reset" : "Setup"), MessageType.DeveloperInfo); - try - { - DisposeRepo(); - Helpers.DeleteDirectory(RelativePath(RepoPath)); - DeletePRList(); - lock (configLock) - { - BackupAndDeleteStaticDirectory(); - } - - var Opts = new CloneOptions() - { - BranchName = BranchName, - RecurseSubmodules = true, - OnTransferProgress = HandleTransferProgress, - OnCheckoutProgress = HandleCheckoutProgress, - CredentialsProvider = GenerateGitCredentials, - }; - - Repository.Clone(RepoURL, RelativePath(RepoPath), Opts); - currentProgress = -1; - LoadRepo(); - - DisableGarbageCollectionNoLock(); - - //create an ssh remote for pushing - Repo.Network.Remotes.Add(SSHPushRemote, RepoURL.Replace("git://", "ssh://").Replace("https://", "ssh://")); - - InitialConfigureRepository(); - - SendMessage("REPO: Clone complete!", MessageType.DeveloperInfo); - WriteInfo("Repository {0}:{1} successfully cloned", EventID.RepoClone); - } - finally - { - currentProgress = -1; - } - } - catch (Exception e) - - { - SendMessage("REPO: Setup failed!", MessageType.DeveloperInfo); - WriteWarning(String.Format("Failed to clone {2}:{0}: {1}", BranchName, e.ToString(), RepoURL), EventID.RepoCloneFail); - } - finally - { - lock (RepoLock) - { - RepoBusy = false; - Cloning = false; - } - } - } - - /// - /// Turns off the gc.auto git config setting - /// - - void DisableGarbageCollectionNoLock() - { - Repo.Config.Set("gc.auto", false); - } - - /// - /// Copies the Static directory to the first available Static_BACKUP path in the then deleted the old directory - /// - void BackupAndDeleteStaticDirectory() - { - var rsd = RelativePath(StaticDirs); - if (Directory.Exists(rsd)) - { - int count = 1; - - var rsbd = RelativePath(StaticBackupDir); - string path = Path.GetDirectoryName(rsbd); - string newFullPath = rsbd; - - while (File.Exists(newFullPath) || Directory.Exists(newFullPath)) - { - string tempDirName = string.Format("{0}({1})", rsbd, count++); - newFullPath = Path.Combine(path, tempDirName); - } - - Helpers.CopyDirectory(rsd, newFullPath); - } - Helpers.DeleteDirectory(rsd); - } - - /// - /// Updates the with the - /// - /// on success, error message on failure - public string UpdateTGS3Json() - { - try - { - if (File.Exists(RelativePath(RepoTGS3SettingsPath))) - File.Copy(RelativePath(RepoTGS3SettingsPath), RelativePath(CachedTGS3SettingsPath), true); - else if (File.Exists(RelativePath(CachedTGS3SettingsPath))) - File.Delete(RelativePath(CachedTGS3SettingsPath)); - } - catch (Exception e) - { - return e.ToString(); - } - return null; - } - - /// - /// Initial setup for the and - /// - void InitialConfigureRepository() - { - Directory.CreateDirectory(RelativePath(StaticDirs)); - UpdateBridgeDll(false); - UpdateTGS3Json(); - var Config = GetCachedRepoConfig(); //RepoBusy is set if we're here - foreach (var I in Config.StaticDirectoryPaths) - { - try - { - var source = Path.Combine(RelativePath(RepoPath), I); - var dest = Path.Combine(RelativePath(StaticDirs), I); - if (Directory.Exists(source)) - Helpers.CopyDirectory(source, dest); - else - Directory.CreateDirectory(dest); - } - catch - { - WriteError("Could not setup static directory: " + I, EventID.RepoConfigurationFail); - } - } - foreach (var I in Config.DLLPaths) - { - try - { - var source = Path.Combine(RelativePath(RepoPath), I); - if (!File.Exists(source)) - { - WriteWarning("Could not find DLL: " + I, EventID.RepoConfigurationFail); - continue; - } - var dest = Path.Combine(RelativePath(StaticDirs), I); - Helpers.CopyFileForceDirectories(source, dest, false); - } - catch - { - WriteError("Could not setup static DLL: " + I, EventID.RepoConfigurationFail); - } - } - } - - /// - public string Setup(string RepoURL, string BranchName) - { - lock (RepoLock) - { - if (RepoBusy) - return "Repo is busy!"; - lock (CompilerLock) - { - if (!CompilerIdleNoLock()) - return "Compiler is running!"; - } - if (DaemonStatus() != DreamDaemonStatus.Offline) - return "DreamDaemon is running!"; - if (RepoURL.Contains("ssh://") && !SSHAuth()) - return String.Format("SSH url specified but either {0} or {1} does not exist in the server directory!", PrivateKeyPath, PublicKeyPath); - RepoBusy = true; - Cloning = true; - new Thread(new ParameterizedThreadStart(Clone)) - { - IsBackground = true //make sure we don't hold up shutdown - }.Start(RepoURL + ' ' + BranchName); - return null; - } - } - - /// - /// Gets the SHA or branch name of the 's HEAD - /// - /// on success, error message on failure - /// If , returns the branch name instead of the SHA - /// If , returns the tracked branch SHA and ignores - /// The SHA or branch name of the 's HEAD - string GetShaOrBranch(out string error, bool branch, bool tracked) - { - lock (RepoLock) - { - if (RepoBusy) - { - error = "Repo is busy!"; - return null; - } - return GetShaOrBranchNoLock(out error, branch, tracked); - } - } - - /// - /// Gets the SHA or branch name of the 's HEAD. Requires - /// - /// on success, error message on failure - /// If , returns the branch name instead of the SHA - /// If , returns the tracked branch SHA and ignores - /// The SHA or branch name of the 's HEAD - string GetShaOrBranchNoLock(out string error, bool branch, bool tracked) - { - var result = LoadRepo(); - if (result != null) - { - error = result; - return null; - } - - try - { - error = null; - if (tracked && Repo.Head.TrackedBranch != null) - return Repo.Head.TrackedBranch.Tip.Sha; - return branch ? Repo.Head.FriendlyName : Repo.Head.Tip.Sha; - } - catch (Exception e) - { - error = e.ToString(); - return null; - } - } - - /// - public string GetHead(bool useTracked, out string error) - { - return GetShaOrBranch(out error, false, useTracked); - } - - /// - public string GetBranch(out string error) - { - return GetShaOrBranch(out error, true, false); - } - - /// - public string GetRemote(out string error) - { - try - { - var res = LoadRepo(); - if (res != null) - { - error = res; - return null; - } - error = null; - return Repo.Network.Remotes["origin"].Url; - } - catch (Exception e) - { - error = e.ToString(); - return null; - } - } - - /// - /// Equivalent of running `git reset --hard` on the repository. Requires - /// - /// If not , reset to this branch instead of HEAD - /// on success, error message on failure - string ResetNoLock(Branch targetBranch) - { - try - { - if (targetBranch != null) - Repo.Reset(ResetMode.Hard, targetBranch.Tip); - else - Repo.Reset(ResetMode.Hard); - return null; - } - catch (Exception e) - { - return e.ToString(); - } - } - - /// - public string Checkout(string sha) - { - if (sha == LiveTrackingBranch) - return "I'm sorry Dave, I'm afraid I can't do that..."; - lock (RepoLock) - { - if (RepoBusy) - return "Repo is busy!"; - var result = LoadRepo(); - if (result != null) - return result; - SendMessage("REPO: Checking out object: " + sha, MessageType.DeveloperInfo); - try - { - if (Repo.Branches[sha] == null) - { - //see if origin has the branch - result = Fetch(); - var trackedBranch = Repo.Branches[String.Format("origin/{0}", sha)]; - if (trackedBranch != null) - { - var newBranch = Repo.CreateBranch(sha, trackedBranch.Tip); - //track it - Repo.Branches.Update(newBranch, b => b.TrackedBranch = trackedBranch.CanonicalName); - } - else if (result != null) - return result; - } - - //if this is a tracked branch, we need to reset to remote first and delete the PR list - ResetNoLock(Repo.Head.TrackedBranch ?? Repo.Head); - DeletePRList(); - - var Opts = new CheckoutOptions() - { - CheckoutModifiers = CheckoutModifiers.Force, - OnCheckoutProgress = HandleCheckoutProgress, - }; - Commands.Checkout(Repo, sha, Opts); - var res = ResetNoLock(null); - UpdateSubmodules(); - SendMessage("REPO: Checkout complete!", MessageType.DeveloperInfo); - WriteInfo("Repo checked out " + sha, EventID.RepoCheckout); - return res; - } - catch (Exception e) - { - SendMessage("REPO: Checkout failed!", MessageType.DeveloperInfo); - WriteWarning(String.Format("Repo checkout of {0} failed: {1}", sha, e.ToString()), EventID.RepoCheckoutFail); - return e.ToString(); - } - } - } - - /// - /// Merges given into the current branch - /// - /// The sha/branch/tag to merge - /// The commit message for the merge commit - /// on success, error message on failure - string MergeBranch(string committish, string mergeMessage) - { - var mo = new MergeOptions() - { - OnCheckoutProgress = HandleCheckoutProgress - }; - if (mergeMessage != null) - { - mo.CommitOnSuccess = false; - mo.FastForwardStrategy = FastForwardStrategy.NoFastForward; - } - var sig = MakeSig(); - var Result = Repo.Merge(committish, sig, mo); - currentProgress = -1; - switch (Result.Status) - { - case MergeStatus.Conflicts: - ResetNoLock(null); - SendMessage(String.Format("REPO: Merge of {0} conflicted, aborted.", committish), MessageType.DeveloperInfo); - return "Merge conflict occurred."; - case MergeStatus.UpToDate: - return RepoErrorUpToDate; - } - if (mergeMessage != null) - Repo.Commit(mergeMessage, sig, sig); - return null; - } - - void PushTestmergeCommit() - { - if (Config.PushTestmergeCommits && SSHAuth()) - { - string NewB = null; - var targetRemote = Repo.Network.Remotes[SSHPushRemote]; - var options = new PushOptions() - { - CredentialsProvider = GenerateGitCredentials - }; - try - { - //now try and push the commit to the remote so they can be referenced - NewB = Repo.CreateBranch(RemoteTempBranchName).CanonicalName; - Repo.Network.Push(targetRemote, NewB, options); //push the branch - Repo.Branches.Remove(NewB); - var removalString = String.Format(":{0}", NewB); - NewB = null; - //we need to delay the second operation a LOT otherwise we get ssh errors - Thread.Sleep(10000); - Repo.Network.Push(targetRemote, removalString, options); //delete the branch - WriteInfo("Pushed reference commit: " + Repo.Head.Tip.Sha, EventID.ReferencePush); - } - catch (Exception e) - { - WriteWarning(String.Format("Failed to push reference commit: {0}. Error: {1}", Repo.Head.Tip.Sha, e.ToString()), EventID.ReferencePush); - } - finally - { - if (NewB != null) - { - //Try to delete the branches regardless - try - { - Repo.Branches.Remove(NewB); - } - catch { } - try - { - Repo.Network.Push(targetRemote, String.Format(":{0}", NewB), options); - } - catch { } - } - } - } - } - - /// - public string Update(bool reset) - { - return UpdateImpl(reset, true); - } - - /// - /// Fetches the origin and merges it into the current branch - /// - /// If , the operation will perform a hard reset instead of a merge - /// If , a return value of will be changed to - /// on success, error message on failure - string UpdateImpl(bool reset, bool successOnUpToDate) - { - lock (RepoLock) - { - if (RepoBusy) - return "Repo is busy!"; - var result = LoadRepo(); - if (result != null) - return result; - try - { - if (Repo.Head == null || !Repo.Head.IsTracking) - return "Cannot update while not on a tracked branch"; - - var res = Fetch(); - if (res != null) - return res; - - var originBranch = Repo.Head.TrackedBranch; - if (!successOnUpToDate && Repo.Head.Tip.Sha == originBranch.Tip.Sha) - return RepoErrorUpToDate; - - SendMessage(String.Format("REPO: Updating origin branch...{0}", reset ? "" : "(Merge)"), MessageType.DeveloperInfo); - - if (reset) - { - var error = ResetNoLock(Repo.Head.TrackedBranch); - UpdateSubmodules(); - if (error != null) - throw new Exception(error); - DeletePRList(); - WriteInfo("Repo hard updated to " + originBranch.Tip.Sha, EventID.RepoHardUpdate); - return error; - } - res = MergeBranch(originBranch.FriendlyName, "Merge origin into current testmerge"); - if (!LocalIsRemote()) //might be fast forward - PushTestmergeCommit(); - if (res != null) - throw new Exception(res); - UpdateSubmodules(); - WriteInfo("Repo merge updated to " + originBranch.Tip.Sha, EventID.RepoMergeUpdate); - return null; - } - catch (Exception E) - { - SendMessage("REPO: Update failed!", MessageType.DeveloperInfo); - WriteWarning(String.Format("Repo{0} update failed", reset ? " hard" : ""), reset ? EventID.RepoHardUpdateFail : EventID.RepoMergeUpdateFail); - return E.ToString(); - } - } - } - - /// - /// Properly updates any git submodules in the repository - /// - private void UpdateSubmodules() - { - var suo = new SubmoduleUpdateOptions - { - Init = true - }; - foreach (var I in Repo.Submodules) - try - { - Repo.Submodules.Update(I.Name, suo); - } - catch (Exception e) - { - try - { - //workaround for https://github.com/libgit2/libgit2/issues/3820 - //kill off the modules/ folder in .git and try again - try - { - Helpers.DeleteDirectory(String.Format("{0}/.git/modules/{1}", RepoPath, I.Path)); - } - catch - { - throw e; - } - Repo.Submodules.Update(I.Name, suo); - var msg = String.Format("I had to reclone submodule {0}. If this is happening a lot find a better hack or fix https://github.com/libgit2/libgit2/issues/3820!", I.Name); - SendMessage(String.Format("REPO: {0}", msg), MessageType.DeveloperInfo); - WriteWarning(msg, EventID.Submodule); - } - catch (Exception ex) - { - WriteError(String.Format("Failed to update submodule {0}! Error: {1}", I.Name, ex.ToString()), EventID.Submodule); - } - } - } - - /// - /// Creates a date and timestamped tag of the current HEAD - /// - /// on success, error message on failure - string CreateBackup() - { - try - { - lock (RepoLock) - { - if (RepoBusy) - return "Repo is busy!"; - var res = LoadRepo(); - if (res != null) - return res; - - //Make sure we don't already have a backup at this commit - var HEAD = Repo.Head.Tip.Sha; - foreach (var T in Repo.Tags) - if (T.Target.Sha == HEAD) - return null; - - var tagName = "TGS-Compile-Backup-" + DateTime.Now.ToString("yyyy-MM-dd--HH.mm.ss"); - var tag = Repo.ApplyTag(tagName); - - if (tag != null) - { - WriteInfo("Repo backup created at tag: " + tagName + " commit: " + HEAD, EventID.RepoBackupTag); - return null; - } - throw new Exception("Tag creation failed!"); - } - } - catch (Exception e) - { - WriteWarning(String.Format("Failed backup tag creation at commit {0}!", Repo.Head.Tip.Sha), EventID.RepoBackupTagFail); - return e.ToString(); - } - } - - /// - /// Lists tags in the repository created by the - /// - /// on success, error message on failure - /// A dictionary of tag title -> commit SHA on success, on failure - public IDictionary ListBackups(out string error) - { - try - { - lock (RepoLock) - { - if (RepoBusy) - { - error = "Repo is busy!"; - return null; - } - error = LoadRepo(); - if (error != null) - return null; - - var res = new Dictionary(); - foreach (var T in Repo.Tags) - if (T.FriendlyName.Contains("TGS")) - res.Add(T.FriendlyName, T.Target.Sha); - return res; - } - } - catch (Exception e) - { - error = e.ToString(); - return null; - } - } - - /// - public string Reset(bool trackedBranch) - { - lock (RepoLock) - { - if (RepoBusy) - return "Repo is busy!"; - var res = LoadRepo() ?? ResetNoLock(trackedBranch ? (Repo.Head.TrackedBranch ?? Repo.Head) : Repo.Head); - if (res == null) - { - SendMessage(String.Format("REPO: Hard reset to {0}branch", trackedBranch ? "tracked " : ""), MessageType.DeveloperInfo); - if (trackedBranch) - DeletePRList(); - WriteInfo(String.Format("Repo branch reset{0}", trackedBranch ? " to tracked branch" : ""), trackedBranch ? EventID.RepoResetTracked : EventID.RepoReset); - return null; - } - WriteWarning(String.Format("Failed to reset{0}: {1}", trackedBranch ? " to tracked branch" : "", res), trackedBranch ? EventID.RepoResetTrackedFail : EventID.RepoResetFail); - return res; - } - } - - /// - /// Creates a commit based off of the configured name and e-mail - /// - /// The created - Signature MakeSig() - { - return new Signature(new Identity(Config.CommitterName, Config.CommitterEmail), DateTimeOffset.Now); - } - - /// - /// Deletes the 's - /// - void DeletePRList() - { - if (File.Exists(RelativePath(PRJobFile))) - try - { - File.Delete(RelativePath(PRJobFile)); - } - catch (Exception e) - { - WriteError("Failed to delete PR list: " + e.ToString(), EventID.RepoPRListError); - } - } - - /// - /// Deserializes the - /// - /// A of . The outer one is keyed by PR# the inner one is keyed by internal s - IDictionary> GetCurrentPRList() - { - if (!File.Exists(RelativePath(PRJobFile))) - return new Dictionary>(); - var rawdata = File.ReadAllText(RelativePath(PRJobFile)); - return JsonConvert.DeserializeObject>>(rawdata); - } - - /// - /// Serializes pull request info into - /// - /// - void SetCurrentPRList(IDictionary> list) - { - var rawdata = JsonConvert.SerializeObject(list, Formatting.Indented); - File.WriteAllText(RelativePath(PRJobFile), rawdata); - } - - /// - public IEnumerable MergePullRequests(IEnumerable pullRequestInfos, bool silent) - { - var results = new List(); - var amount = pullRequestInfos.Count(); - if (amount < 1) - return results; - - if (!silent) { - var messages = new List(); - foreach (var I in pullRequestInfos) - { - var shortSha = I.Sha?.Substring(0, Math.Min(I.Sha.Length, 7)); - messages.Add(String.Format("#{0}{1}", I.Number, shortSha != null ? String.Format(" at commit {0}", shortSha) : shortSha)); - } - SendMessage(String.Format("REPO: Merging PR{0} {1}...", amount == 1 ? "" : "s", String.Join(", ", messages)), MessageType.DeveloperInfo); - } - - bool foundBad = false; - foreach (var I in pullRequestInfos) - { - var res = MergePullRequest(I.Number, I.Sha, true); - if (res != null) - foundBad = true; - results.Add(res); - } - return foundBad ? results : null; - } - - /// - public string MergePullRequest(int PRNumber, string atSHA, bool silent) - { - lock (RepoLock) - { - if (RepoBusy) - return "Repo is busy!"; - var result = LoadRepo(); - if (result != null) - return result; - if(!silent) - SendMessage(String.Format("REPO: Merging PR #{0}{1}...", PRNumber, atSHA != null ? String.Format(" at commit {0}", atSHA): ""), MessageType.DeveloperInfo); - result = ResetNoLock(null); - if (result != null) - return result; - try - { - //only supported with github - var remoteUrl = Repo.Network.Remotes["origin"].Url; - if (!remoteUrl.Contains("github.com")) - return "Only supported with Github based repositories."; - - - var Refspec = new List(); - var PRBranchName = String.Format("pr-{0}", PRNumber); - var LocalBranchName = String.Format("pull/{0}/headrefs/heads/{1}", PRNumber, PRBranchName); - Refspec.Add(String.Format("pull/{0}/head:{1}", PRNumber, PRBranchName)); - var logMessage = ""; - - //Need to delete the branch first in case of rebase - Repo.Branches.Remove(LocalBranchName); - Repo.Branches.Remove(PRBranchName); - - Commands.Fetch(Repo, "origin", Refspec, GenerateFetchOptions(), logMessage); //shitty api has no failure state for this - - currentProgress = -1; - - var branch = Repo.Branches[LocalBranchName]; - if (branch == null) - { - SendMessage(String.Format("REPO: PR {0}could not be fetched. Does it exist?", silent ? String.Format("#{0} ", PRNumber) : ""), MessageType.DeveloperInfo); - return String.Format("PR #{0} could not be fetched. Does it exist?", PRNumber); - } - - //give it a better name - branch = Repo.CreateBranch(PRBranchName, branch.Tip); - Repo.Branches.Remove(LocalBranchName); - - if (atSHA != null) - { - //find the commit - Commit commit = null; - string error = null; - try - { - commit = Repo.Lookup(atSHA); - } - catch (Exception e) - { - error = e.ToString(); - } - if (commit == null) - { - SendMessage("REPO: Commit could not be found, aborting!", MessageType.DeveloperInfo); - return error ?? String.Format("Commit {0} could not be found in the repository!", atSHA); - } - } - - //so we'll know if this fails - var Result = MergeBranch(atSHA ?? branch.CanonicalName, String.Format("[ci skip] Test merge commit for pull request #{0}{1}Server Instance: {2}", PRNumber, Environment.NewLine, Config.Name)); - - if (Result == null) - try - { - UpdateSubmodules(); - } - catch (Exception e) - { - Result = e.ToString(); - } - - if (Result == null) - { - WriteInfo(String.Format("Merged pull request #{0}", PRNumber), EventID.RepoPRMerge); - try - { - var CurrentPRs = GetCurrentPRList(); - var PRNumberString = PRNumber.ToString(); - CurrentPRs.Remove(PRNumberString); - var newPR = new Dictionary(); - - //do some excellent remote fuckery here to get the api page - var prAPI = remoteUrl; - prAPI = prAPI.Replace("/.git", ""); - prAPI = prAPI.Replace(".git", ""); - prAPI = prAPI.Replace("git:", "https:"); - prAPI = prAPI.Replace("github.com", "api.github.com/repos"); - prAPI += "/pulls/" + PRNumberString + ".json"; - string json; - //tls1.2 meme - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - using (var wc = new WebClient()) - { - wc.Headers.Add("user-agent", "TGS.Server"); - json = wc.DownloadString(prAPI); - } - - var dick = JsonConvert.DeserializeObject>(json); - var user = ((JObject)dick["user"]).ToObject>(); - - newPR.Add("commit", atSHA ?? branch.Tip.Sha); - newPR.Add("author", (string)user["login"]); - newPR.Add("title", (string)dick["title"]); - CurrentPRs.Add(PRNumberString, newPR); - SetCurrentPRList(CurrentPRs); - } - catch (Exception e) - { - WriteError("Failed to update PR list", EventID.RepoPRListError); - return "PR Merged, JSON update failed: " + e.ToString(); - } - - PushTestmergeCommit(); - } - return Result; - } - catch (Exception E) - { - SendMessage("REPO: PR merge failed!", MessageType.DeveloperInfo); - WriteWarning(String.Format("Failed to merge pull request #{0}: {1}", PRNumber, E.ToString()), EventID.RepoPRMergeFail); - return E.ToString(); - } - } - } - - /// - public List MergedPullRequests(out string error) - { - lock (RepoLock) - { - if (RepoBusy) - { - error = "Repo is busy!"; - return null; - } - var result = LoadRepo(); - if (result != null) - { - error = result; - return null; - } - try - { - var PRRawData = GetCurrentPRList(); - var output = new List(); - foreach (var I in GetCurrentPRList()) - output.Add(new PullRequestInfo(Convert.ToInt32(I.Key), I.Value["author"], I.Value["title"], I.Value["commit"])); - error = null; - return output; - } - catch (Exception e) - { - error = e.ToString(); - return null; - } - } - } - - /// - public string GetCommitterName() - { - return Config.CommitterName; - } - - /// - public void SetCommitterName(string newName) - { - Config.CommitterName = newName; - } - - /// - public string GetCommitterEmail() - { - return Config.CommitterEmail; - } - - /// - public void SetCommitterEmail(string newEmail) - { - Config.CommitterEmail = newEmail; - } - - /// - public string SynchronizePush() - { - lock (RepoLock) - { - if (RepoBusy) - return "Repo is busy!"; - var Config = GetCachedRepoConfig(); - if (Config == null) - return "Error reading changelog configuration"; - if (!Config.ChangelogSupport || !SSHAuth()) - return null; - var res = LoadRepo(); - if (res != null) - return res; - return LocalIsRemote() ? Commit(Config) ?? Push() : "Can't push changelog: HEAD does not match tracked remote branch"; - } - } - - /// - /// Create that Prune and have the appropriate credentials and progress handler - /// - /// Properly configured - FetchOptions GenerateFetchOptions() - { - return new FetchOptions() - { - CredentialsProvider = GenerateGitCredentials, - OnTransferProgress = HandleTransferProgress, - Prune = true, - }; - } - - /// - /// Fetches origin - /// - /// null on success, error message on failure - string Fetch() - { - try - { - string logMessage = ""; - var R = Repo.Network.Remotes["origin"]; - IEnumerable refSpecs = R.FetchRefSpecs.Select(X => X.Specification); - Commands.Fetch(Repo, R.Name, refSpecs, GenerateFetchOptions(), logMessage); - return null; - } - catch (Exception e) - { - return e.ToString(); - } - } - - /// - /// Check if the current HEAD matches the tracked remote branch HEAD - /// - /// if the current HEAD matches the tracked remote branch HEAD, otherwise - bool LocalIsRemote() - { - lock (RepoLock) - { - if (RepoBusy) - return false; - if (LoadRepo() != null) - return false; - if (Fetch() != null) - return false; - try - { - return Repo.Head.IsTracking && Repo.Head.TrackedBranch.Tip.Sha == Repo.Head.Tip.Sha; - } - catch - { - return false; - } - } - } - - /// - /// Create a commit based on a . Requires and - /// - /// A with - /// on success, error message on failure - string Commit(RepoConfig Config) - { - try - { - // Stage the file - foreach (var I in Config.PathsToStage) - Commands.Stage(Repo, I); - - if (Repo.RetrieveStatus().Staged.Count() == 0) //nothing to commit - return null; - - // Create the committer's signature and commit - var authorandcommitter = MakeSig(); - - // Commit to the repository - WriteInfo(String.Format("Commit {0} created from changelogs", Repo.Commit(CommitMessage, authorandcommitter, authorandcommitter)), EventID.RepoCommit); - DeletePRList(); - return null; - } - catch (Exception e) - { - WriteWarning("Repo commit failed: " + e.ToString(), EventID.RepoCommitFail); - return e.ToString(); - } - } - - /// - /// Push HEAD to . Requires repo be locked, , , and - /// - /// on success, error message on failure - string Push() - { - try - { - var options = new PushOptions() - { - CredentialsProvider = GenerateGitCredentials, - }; - Repo.Network.Push(Repo.Network.Remotes[SSHPushRemote], Repo.Head.CanonicalName, options); - WriteInfo("Repo pushed up to commit: " + Repo.Head.Tip.Sha, EventID.RepoPush); - return null; - } - catch (Exception e) - { - WriteWarning("Repo push failed: " + e.ToString(), EventID.RepoPushFail); - return e.ToString(); - } - } - - /// - /// Check if the is configured for SSH pushing - /// - /// if the see cref="Instance"/> is configured for SSH pushing, otherwise - bool SSHAuth() - { - return File.Exists(RelativePath(PrivateKeyPath)) && File.Exists(RelativePath(PublicKeyPath)); - } - - /// - /// SSH credentials callback. Properly sets up SSH keys for an authorization operation - /// - /// Ignored - /// The username to use for the operation - /// The for the operation - /// The proper for the operation - Credentials GenerateGitCredentials(string url, string usernameFromUrl, SupportedCredentialTypes types) - { - var user = usernameFromUrl ?? "git"; - if (types == SupportedCredentialTypes.UsernameQuery) - return new UsernameQueryCredentials() - { - Username = user, - }; - return new SshUserKeyCredentials() - { - Username = user, - PrivateKey = RelativePath(PrivateKeyPath), - PublicKey = RelativePath(PublicKeyPath), - Passphrase = "", - }; - } - - /// - public string GenerateChangelog(out string error) - { - return GenerateChangelogImpl(out error); - } - - /// - /// Updates the html changelog - /// - /// on success, error on failure - /// If , prevents a recursive call to this function after updating pip dependencies - /// The output of the python script - public string GenerateChangelogImpl(out string error, bool recurse = false) - { - var RConfig = GetCachedRepoConfig(); - if (RConfig == null) - { - error = null; - return "Error loading changelog config!"; - } - if (!RConfig.ChangelogSupport) - { - error = null; - return null; - } - - string ChangelogPy = RConfig.PathToChangelogPy; - if (!Exists()) - { - error = "Repo does not exist!"; - return null; - } - - lock (RepoLock) - { - if (RepoBusy) - { - error = "Repo is busy!"; - return null; - } - if (!File.Exists(Path.Combine(RelativePath(RepoPath), ChangelogPy))) - { - error = "Missing changelog generation script!"; - return null; - } - - var pp = Server.Config.PythonPath; - var PythonFile = Path.Combine(pp, "python.exe"); - if (!File.Exists(PythonFile)) - { - error = "Cannot locate python!"; - return null; - } - try - { - string result; - int exitCode; - using (var python = new Process()) - { - python.StartInfo.FileName = PythonFile; - python.StartInfo.Arguments = String.Format("{0} {1}", ChangelogPy, RConfig.ChangelogPyArguments); - python.StartInfo.UseShellExecute = false; - python.StartInfo.WorkingDirectory = new DirectoryInfo(RelativePath(RepoPath)).FullName; - python.StartInfo.RedirectStandardOutput = true; - python.Start(); - using (StreamReader reader = python.StandardOutput) - { - result = reader.ReadToEnd(); - - } - python.WaitForExit(); - exitCode = python.ExitCode; - } - if (exitCode != 0) - { - if (recurse || RConfig.PipDependancies.Count == 0) - { - error = "Script failed!"; - return result; - } - //update pip deps and try again - - string PipFile = Path.Combine(pp, "scripts", "pip.exe"); - foreach(var I in RConfig.PipDependancies) - using (var pip = new Process()) - { - pip.StartInfo.FileName = PipFile; - pip.StartInfo.Arguments = "install " + I; - pip.StartInfo.UseShellExecute = false; - pip.StartInfo.RedirectStandardOutput = true; - pip.Start(); - using (StreamReader reader = pip.StandardOutput) - { - result += "\r\n---BEGIN-PIP-OUTPUT---\r\n" + reader.ReadToEnd(); - } - pip.WaitForExit(); - if (pip.ExitCode != 0) - { - error = "Script and pip failed!"; - return result; - } - } - //and recurse - return GenerateChangelogImpl(out error, true); - } - error = null; - WriteInfo("Changelog generated" + error, EventID.RepoChangelog); - return result; - } - catch (Exception e) - { - error = e.ToString(); - WriteWarning("Changelog generation failed: " + error, EventID.RepoChangelogFail); - return null; - } - } - } - - /// - public void SetAutoUpdateInterval(ulong newInterval) - { - lock (autoUpdateTimer) - { - autoUpdateTimer.Stop(); - if (newInterval > 0) { - autoUpdateTimer.Interval = newInterval * 60 * 1000; //convert from minutes to ms - autoUpdateTimer.Start(); - } - } - Config.AutoUpdateInterval = newInterval; - } - - /// - public ulong AutoUpdateInterval() - { - return Config.AutoUpdateInterval; - } - - /// - /// Runs on the configured of and tries to and the - /// - /// A - /// The event arguments - private void AutoUpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) - { - if (UpdateImpl(true, false) == null) - { - Compile(true); - } - } - - /// - /// Updates with the staged 's SHA - /// - /// The commit SHA that was staged - void UpdateLiveSha(string newSha) - { - if (LoadRepo() != null) - return; - var B = Repo.Branches[LiveTrackingBranch]; - if (B != null) - Repo.Branches.Remove(B); - Repo.CreateBranch(LiveTrackingBranch, newSha); - } - - /// - /// Gets the current staged or live commit SHA - /// - /// The current staged or live commit SHA - public string LiveSha() - { - var B = Repo.Branches[LiveTrackingBranch]; - return B != null ? B.Tip.Sha : "UNKNOWN"; - } - - /// - public bool PushTestmergeCommits() - { - return Config.PushTestmergeCommits; - } - - /// - public void SetPushTestmergeCommits(bool newValue) - { - Config.PushTestmergeCommits = newValue; - } - } -} diff --git a/legacy/V3/TGS.Server/InstanceConfig.cs b/legacy/V3/TGS.Server/InstanceConfig.cs deleted file mode 100644 index 2e8bd592a3..0000000000 --- a/legacy/V3/TGS.Server/InstanceConfig.cs +++ /dev/null @@ -1,232 +0,0 @@ -using Newtonsoft.Json; -using System.IO; -using TGS.Interface; - -namespace TGS.Server -{ - /// - /// Configuration settings for a - /// - public interface IInstanceConfig - { - /// - /// The directory this is for - /// - string Directory { get; } - - /// - /// Actual version of the . Migrated up via - /// - ulong Version { get; } - - /// - /// The name of the - /// - string Name { get; set; } - - /// - /// If the is active - /// - bool Enabled { get; set; } - - /// - /// The name of the .dme/.dmb the uses - /// - string ProjectName { get; set; } - - /// - /// The port the runs on - /// - ushort Port { get; set; } - - /// - /// The level for the - /// - DreamDaemonSecurity Security { get; set; } - - /// - /// Whether or not the should immediately start DreamDaemon when activated - /// - bool Autostart { get; set; } - - /// - /// Whether or not DreamDaemon allows connections from webclients - /// - bool Webclient { get; set; } - - /// - /// Author and committer name for synchronize commits - /// - string CommitterName { get; set; } - /// - /// Author and committer e-mail for synchronize commits - /// - string CommitterEmail { get; set; } - - /// - /// Encrypted serialized s - /// - string ChatProviderData { get; set; } - - /// - /// Entropy for - /// - string ChatProviderEntropy { get; set; } - - /// - /// If the should reattach to a running DreamDaemon - /// - bool ReattachRequired { get; set; } - - /// - /// The of the runnning DreamDaemon - /// - int ReattachProcessID { get; set; } - - /// - /// The port the runnning DreamDaemon was launched on - /// - ushort ReattachPort { get; set; } - - /// - /// The serviceCommsKey the runnning DreamDaemon was launched on - /// - string ReattachCommsKey { get; set; } - - /// - /// The API version of the runnning DreamDaemon - /// - string ReattachAPIVersion { get; set; } - - /// - /// The user group allowed to use the - /// - string AuthorizedUserGroupSID { get; set; } - - /// - /// The auto update interval for the - /// - ulong AutoUpdateInterval { get; set; } - - /// - /// Whether or not testmerge commits are published to a temporary remote branch - /// - bool PushTestmergeCommits { get; set; } - - /// - /// Saves the to it's - /// - void Save(); - } - - /// - public class InstanceConfig : IInstanceConfig - { - /// - /// The name the file is saved as in the - /// - [JsonIgnore] - public const string JSONFilename = "Instance.json"; - - /// - /// The current version of the config - /// - [JsonIgnore] - protected const ulong CurrentVersion = 0; //Literally any time you add/deprecated a field, this number needs to be bumped - - /// - [JsonIgnore] - public string Directory { get; private set; } - - /// - public ulong Version { get; protected set; } = CurrentVersion; - - /// - public string Name { get; set; } = "TG Station Server"; - - /// - public bool Enabled { get; set; } = true; - - /// - public string ProjectName { get; set; } = "tgstation"; - - /// - public ushort Port { get; set; } = 1337; - - /// - public DreamDaemonSecurity Security { get; set; } = DreamDaemonSecurity.Trusted; - - /// - public bool Autostart { get; set; } = false; - - /// - public bool Webclient { get; set; } = false; - - /// - public string CommitterName { get; set; } = "tgstation-server"; - - /// - public string CommitterEmail { get; set; } = "tgstation-server@tgstation13.org"; - - /// - public string ChatProviderData { get; set; } = Instance.UninitializedString; - - /// - public string ChatProviderEntropy { get; set; } - - /// - public bool ReattachRequired { get; set; } = false; - - /// - public int ReattachProcessID { get; set; } - - /// - public ushort ReattachPort { get; set; } - - /// - public string ReattachCommsKey { get; set; } - - /// - public string ReattachAPIVersion { get; set; } - - /// - public string AuthorizedUserGroupSID { get; set; } = null; - - /// - public ulong AutoUpdateInterval { get; set; } = 0; - - /// - public bool PushTestmergeCommits { get; set; } = false; - - /// - /// Construct a for a at - /// - /// The path to the - public InstanceConfig(string path) - { - Directory = path; - } - - /// - public void Save() - { - var data = JsonConvert.SerializeObject(this, Formatting.Indented); - var path = Path.Combine(Directory, JSONFilename); - File.WriteAllText(path, data); - } - - /// - /// Loads and migrates an from a at - /// - /// The path to the directory - /// The migrated - public static IInstanceConfig Load(string path) - { - var configtext = File.ReadAllText(Path.Combine(path, JSONFilename)); - var res = JsonConvert.DeserializeObject(configtext); - res.Directory = path; - res.MigrateToCurrentVersion(); - return res; - } - } -} diff --git a/legacy/V3/TGS.Server/MessageType.cs b/legacy/V3/TGS.Server/MessageType.cs deleted file mode 100644 index 72d930bb0e..0000000000 --- a/legacy/V3/TGS.Server/MessageType.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace TGS.Server -{ - /// - /// Type of chat message, these may be OR'd together - /// - [Flags] - enum MessageType - { - /// - /// Send message to the admin channels - /// - AdminInfo = 1, - /// - /// Send message to the game channels - /// - GameInfo = 2, - /// - /// Send message to the watchdog channels - /// - WatchdogInfo = 4, - /// - /// Send message to the coder channels - /// - DeveloperInfo = 8, - } -} diff --git a/legacy/V3/TGS.Server/ProcessExtension.cs b/legacy/V3/TGS.Server/ProcessExtension.cs deleted file mode 100644 index 0d24a8351c..0000000000 --- a/legacy/V3/TGS.Server/ProcessExtension.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace TGS.Server -{ - /// - /// Helpers to ing and a . Lightly massaged code from https://stackoverflow.com/a/13109774. Documentation linked from MSDN on 20/10/2017 - /// - static class ProcessExtension - { - /// - /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx - /// - enum ThreadAccess : int - { - SUSPEND_RESUME = (0x0002), - } - /// - /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms684335(v=vs.85).aspx - /// - [DllImport("kernel32.dll")] - static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); - /// - /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx - /// - [DllImport("kernel32.dll")] - static extern bool CloseHandle(IntPtr hObject); - /// - /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686345(v=vs.85).aspx - /// - [DllImport("kernel32.dll")] - static extern uint SuspendThread(IntPtr hThread); - /// - /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms685086(v=vs.85).aspx - /// - [DllImport("kernel32.dll")] - static extern int ResumeThread(IntPtr hThread); - - /// - /// Suspends all threads for a running - /// - /// The to suspend - public static void Suspend(this Process process) - { - foreach (ProcessThread thread in process.Threads) - { - var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id); - if (pOpenThread == IntPtr.Zero) - continue; - SuspendThread(pOpenThread); - CloseHandle(pOpenThread); - } - } - - /// - /// Resumes all threads for a running - /// - /// The to suspend - public static void Resume(this Process process) - { - foreach (ProcessThread thread in process.Threads) - { - var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id); - if (pOpenThread == IntPtr.Zero) - continue; - ResumeThread(pOpenThread); - CloseHandle(pOpenThread); - } - } - } -} diff --git a/legacy/V3/TGS.Server/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Server/Properties/AssemblyInfo.cs deleted file mode 100644 index 5dc00523b0..0000000000 --- a/legacy/V3/TGS.Server/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TGStation Server")] -[assembly: AssemblyDescription("Server management suite for running BYOND games")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f32eda25-0855-411c-af5e-f0d042917e2d")] diff --git a/legacy/V3/TGS.Server/RepoConfig.cs b/legacy/V3/TGS.Server/RepoConfig.cs deleted file mode 100644 index edbcf32622..0000000000 --- a/legacy/V3/TGS.Server/RepoConfig.cs +++ /dev/null @@ -1,151 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace TGS.Server -{ - /// - /// Repository specific information for a - /// - sealed class RepoConfig : IEquatable - { - /// - /// If this json is setup to support - /// - public readonly bool ChangelogSupport; - /// - /// Path to the repository's changelog generator script - /// - public readonly string PathToChangelogPy; - /// - /// Arguments for the changelog gennerator script - /// - public readonly string ChangelogPyArguments; - /// - /// List of python pip dependencies for the changelog generator script - /// - public readonly IList PipDependancies = new List(); - /// - /// Paths to commit and push to the remote repository - /// - public readonly IList PathsToStage = new List(); - /// - /// Directory's whose contents should not be touched when the updates - /// - public readonly IList StaticDirectoryPaths = new List(); - /// - /// DLL's used by DreamDaemon call()() operations. Must be handled as symlinks to avoid lockups during update operations - /// - public readonly IList DLLPaths = new List(); - - /// - /// Construct a RepoConfig - /// - /// Path to the config JSON to use - public RepoConfig(string path) - { - if (!File.Exists(path)) - return; - var rawdata = File.ReadAllText(path); - var json = JsonConvert.DeserializeObject>(rawdata); - try - { - var details = ((JObject)json["changelog"]).ToObject>(); - PathToChangelogPy = (string)details["script"]; - ChangelogPyArguments = (string)details["arguments"]; - ChangelogSupport = true; - try - { - PipDependancies = LoadArray(details["pip_dependancies"]); - } - catch { } - } - catch - { - ChangelogSupport = false; - } - try - { - PathsToStage = LoadArray(json["synchronize_paths"]); - } - catch { } - try - { - StaticDirectoryPaths = LoadArray(json["static_directories"]); - } - catch { } - try - { - DLLPaths = LoadArray(json["dlls"]); - } - catch { } - } - - /// - /// Convert an array of s to a of s - /// - /// The array to convert - /// - private static IList LoadArray(object o) - { - var res = new List(); - foreach (var I in ((JArray)o)) - res.Add(I.ToObject()); - return res; - } - - /// - public override bool Equals(object obj) - { - return Equals(obj as RepoConfig); - } - - /// - /// Check if two s have the same contents - /// - /// The first - /// The second - /// if the s match, otherwise - private static bool ListEquals(IList A, IList B) - { - return A.All(B.Contains) && A.Count == B.Count; - } - - public bool Equals(RepoConfig other) - { - return ChangelogSupport == other.ChangelogSupport - && PathToChangelogPy == other.PathToChangelogPy - && ChangelogPyArguments == other.ChangelogPyArguments - && ListEquals(PipDependancies, other.PipDependancies) - && ListEquals(PathsToStage, other.PathsToStage) - && ListEquals(StaticDirectoryPaths, other.StaticDirectoryPaths) - && ListEquals(DLLPaths, other.DLLPaths); - } - - public override int GetHashCode() - { - var hashCode = 1890628544; - hashCode = hashCode * -1521134295 + ChangelogSupport.GetHashCode(); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(PathToChangelogPy); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ChangelogPyArguments); - hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(PipDependancies); - hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(PathsToStage); - hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(StaticDirectoryPaths); - hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(DLLPaths); - return hashCode; - } - - public static bool operator ==(RepoConfig config1, RepoConfig config2) - { - return EqualityComparer.Default.Equals(config1, config2); - } - - public static bool operator !=(RepoConfig config1, RepoConfig config2) - { - return !(config1 == config2); - } - } -} diff --git a/legacy/V3/TGS.Server/Security/AuthenticationHeaderDecoder.cs b/legacy/V3/TGS.Server/Security/AuthenticationHeaderDecoder.cs deleted file mode 100644 index ea58cd043f..0000000000 --- a/legacy/V3/TGS.Server/Security/AuthenticationHeaderDecoder.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.IdentityModel.Policy; -using System.Runtime.InteropServices; -using System.Security.Principal; -using System.ServiceModel; -using System.ServiceModel.Channels; - -namespace TGS.Server.Security -{ - sealed class AuthenticationHeaderDecoder : ServiceAuthenticationManager - { - /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx - /// - [DllImport("advapi32.dll", SetLastError = true)] - static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); - /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx - /// - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool CloseHandle(IntPtr handle); - - /// - /// Authenticates a given - /// - /// The default s - /// The at which the message was received. - /// The to be authenticated - /// A of s. Either or one containing a single if this function successfully creates one - public override ReadOnlyCollection Authenticate(ReadOnlyCollection authPolicy, Uri listenUri, ref Message message) - { - try - { - var userPosition = message.Headers.FindHeader("Username", "http://tempuri.org"); - var passPosition = message.Headers.FindHeader("Password", "http://tempuri.org"); - - if (userPosition != -1 && passPosition != -1) - { - var user = message.Headers.GetHeader(userPosition); - var pass = message.Headers.GetHeader(passPosition); - - var splits = user.Split('\\'); - - var res = LogonUser(splits.Length > 1 ? splits[1] : splits[0], splits.Length > 1 ? splits[0] : null, pass, 3 /*LOGON32_LOGON_NETWORK*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out IntPtr token); - if (res) - { - //IMPORTANT: logoff the user after all is said and done or they'll stay logged in on the system for as long as we are running - OperationContext.Current.OperationCompleted += (a, b) => { CloseHandle(token); }; - return new ReadOnlyCollection(new List { new WindowsAuthorizationPolicy(new WindowsIdentity(token)) }); - } - } - } - catch { } - return authPolicy; - } - } -} diff --git a/legacy/V3/TGS.Server/Security/RootAuthorizationManager.cs b/legacy/V3/TGS.Server/Security/RootAuthorizationManager.cs deleted file mode 100644 index 809e87078c..0000000000 --- a/legacy/V3/TGS.Server/Security/RootAuthorizationManager.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Principal; -using System.ServiceModel; -using TGS.Interface.Components; - -namespace TGS.Server.Security -{ - /// - /// A used to determine only if the caller is an admin - /// - sealed class RootAuthorizationManager : ServiceAuthorizationManager - { - string LastSeenUser; - public static readonly IList InstanceAuthManagers = new List(); - protected override bool CheckAccessCore(OperationContext operationContext) - { - var contract = operationContext.EndpointDispatcher.ContractName; - if (contract == typeof(ITGConnectivity).Name) //always allow connectivity checks - return true; - - var windowsIdent = operationContext.ServiceSecurityContext.WindowsIdentity; - - var wp = new WindowsPrincipal(windowsIdent); - //first allow admins - var authSuccess = wp.IsInRole(WindowsBuiltInRole.Administrator); - if(!authSuccess && contract == typeof(ITGLanding).Name) - lock(InstanceAuthManagers) - return InstanceAuthManagers.Any(x => x.CheckAccess(operationContext)); - - var user = windowsIdent.Name; - if (LastSeenUser != user) - { - LastSeenUser = user; - Server.Logger.WriteAccess(String.Format("Root access from: {0}", user), authSuccess, Server.LoggingID); - } - return authSuccess; - } - } -} diff --git a/legacy/V3/TGS.Server/Security/WindowsAuthorizationPolicy.cs b/legacy/V3/TGS.Server/Security/WindowsAuthorizationPolicy.cs deleted file mode 100644 index bc070f2c6a..0000000000 --- a/legacy/V3/TGS.Server/Security/WindowsAuthorizationPolicy.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IdentityModel.Claims; -using System.IdentityModel.Policy; -using System.Security.Principal; - -namespace TGS.Server.Security -{ - /// - /// Implements an for - /// - sealed class WindowsAuthorizationPolicy : IAuthorizationPolicy - { - /// - /// The to use - /// - readonly WindowsIdentity identity; - - /// - /// Construct a - /// - /// The value of - public WindowsAuthorizationPolicy(WindowsIdentity ident) - { - identity = ident; - } - - /// - /// Unused implementation of - /// - public ClaimSet Issuer => throw new NotImplementedException(); - - /// - /// Unused implementation of - /// - public string Id => throw new NotImplementedException(); - - /// - /// Attaches to 's properties - /// - /// The - /// A state object - /// - public bool Evaluate(EvaluationContext evaluationContext, ref object state) - { - evaluationContext.Properties["Identities"] = new List { identity }; - return true; - } - } -} diff --git a/legacy/V3/TGS.Server/Server.cs b/legacy/V3/TGS.Server/Server.cs deleted file mode 100644 index 42351977b7..0000000000 --- a/legacy/V3/TGS.Server/Server.cs +++ /dev/null @@ -1,700 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Security.Principal; -using System.ServiceModel; -using TGS.Interface; -using TGS.Interface.Components; -using TGS.Server.Security; - -namespace TGS.Server -{ - /// - /// The windows service the application runs as - /// - [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)] - public sealed class Server : ITGSService, ITGConnectivity, ITGLanding, ITGInstanceManager, IDisposable - { - /// - /// The logging ID used for events - /// - public const byte LoggingID = 0; - - /// - /// The directory to use when importing a .NET settings based config - /// - public const string MigrationConfigDirectory = "C:\\TGSSettingUpgradeTempDir"; - - /// - /// The service version based on the 's - /// - public static readonly string VersionString = String.Format("/tg/station 13 Server v{0}", Assembly.GetExecutingAssembly().GetName().Version); - - /// - /// Singleton - /// - public static ILogger Logger { get; private set; } - - /// - /// The for the - /// - public static ServerConfig Config { get; private set; } - - /// - /// The directory to load and save s to - /// - static readonly string DefaultConfigDirectory = Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TGS.Server")).FullName; - - /// - /// Begins user impersonation to allow proper restricted file access - /// - /// A representing the impersonation - public static WindowsImpersonationContext BeginImpersonation() - { - return WindowsIdentity.Impersonate(OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Token); - } - - /// - /// Checks an for illegal characters - /// - /// The name to check - /// if contains no illegal characters, error message otherwise - static string CheckInstanceName(string instanceName) - { - char[] bannedCharacters = { ';', '&', '=', '%' }; - foreach (var I in bannedCharacters) - if (instanceName.Contains(I.ToString())) - return "Instance names may not contain the following characters: ';', '&', '=', or '%'"; - return null; - } - - /// - /// The WCF host that contains connects to - /// - ServiceHost serviceHost; - /// - /// Map of to the respective hosting the - /// - IDictionary hosts; - /// - /// List of s in use - /// - IList UsedLoggingIDs = new List(); - - /// - /// Construct a - /// - /// Command line arguments for the - /// The to use - public Server(string[] args, ILogger logger) - { - Logger = logger; - - Environment.CurrentDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name)).FullName; //MOVE THIS POINTER BECAUSE ONE TIME I ALMOST ACCIDENTALLY NUKED MYSELF BY REFACTORING! http://imgur.com/zvGEpJD.png - - SetupConfig(); - - ChangePortFromCommandLine(args); - - SetupService(); - - SetupInstances(); - - OnlineAllHosts(); - } - - /// - /// Enumerates configured s. Detaches those that fail to load - /// - /// Each configured - IEnumerable GetInstanceConfigs() - { - var pathsToRemove = new List(); - lock (this) - { - var IPS = Config.InstancePaths; - foreach (var I in IPS) - { - IInstanceConfig ic; - try - { - ic = InstanceConfig.Load(I); - } - catch (Exception e) - { - Logger.WriteError(String.Format("Unable load instance config at path {0}. Error: {1} Detaching...", I, e.ToString()), EventID.InstanceInitializationFailure, LoggingID); - pathsToRemove.Add(I); - continue; - } - yield return ic; - } - foreach (var I in pathsToRemove) - IPS.Remove(I); - } - } - - /// - /// Overrides and saves the configured if requested by command line parameters - /// - /// The command line parameters for the - void ChangePortFromCommandLine(string[] args) - { - for (var I = 0; I < args.Length - 1; ++I) - if (args[I].ToLower() == "-port") - { - try - { - var res = Convert.ToUInt16(args[I + 1]); - if (res == 0) - throw new Exception("Cannot bind to port 0"); - Config.RemoteAccessPort = res; - } - catch (Exception e) - { - throw new Exception("Invalid argument for \"-port\"", e); - } - Config.Save(DefaultConfigDirectory); - break; - } - } - - /// - /// Upgrades up the service configuration - /// - void SetupConfig() - { - Directory.CreateDirectory(DefaultConfigDirectory); - try - { - Config = ServerConfig.Load(DefaultConfigDirectory); - } - catch - { - try - { - //assume we're upgrading - Config = ServerConfig.Load(MigrationConfigDirectory); - Config.Save(DefaultConfigDirectory); - Helpers.DeleteDirectory(MigrationConfigDirectory); - } - catch - { - //new baby - Config = new ServerConfig(); - } - } - } - - /// - /// Creates the for - /// - void SetupService() - { - serviceHost = CreateHost(this, Definitions.MasterInterfaceName); - AddEndpoint(serviceHost, typeof(ITGConnectivity)); - AddEndpoint(serviceHost, typeof(ITGInstanceManager)); - AddEndpoint(serviceHost, typeof(ITGLanding)); - AddEndpoint(serviceHost, typeof(ITGSService)); - serviceHost.Authorization.ServiceAuthorizationManager = new RootAuthorizationManager(); //only admins can diddle us - serviceHost.Authentication.ServiceAuthenticationManager = new AuthenticationHeaderDecoder(); - } - - /// - /// Opens all created s - /// - void OnlineAllHosts() - { - serviceHost.Open(); - foreach (var I in hosts) - I.Value.Open(); - } - - /// - /// Creates a for using the default pipe, CloseTimeout for the , and the configured - /// - /// The - /// The URL to access components on the - /// The created - static ServiceHost CreateHost(object singleton, string endpointPostfix) - { - return new ServiceHost(singleton, new Uri[] { new Uri(String.Format("net.pipe://localhost/{0}", endpointPostfix)), new Uri(String.Format("https://localhost:{0}/{1}", Config.RemoteAccessPort, endpointPostfix)) }) - { - CloseTimeout = new TimeSpan(0, 0, 5) - }; - } - - /// - /// Creates s for all s as listed in , detaches bad ones - /// - void SetupInstances() - { - hosts = new Dictionary(); - var pathsToRemove = new List(); - var seenNames = new List(); - foreach (var I in GetInstanceConfigs()) - { - if (seenNames.Contains(I.Name)) - { - Logger.WriteError(String.Format("Instance at {0} has a duplicate name! Detaching...", I.Directory), EventID.InstanceInitializationFailure, LoggingID); - pathsToRemove.Add(I.Directory); - } - if (I.Enabled && SetupInstance(I) == null) - pathsToRemove.Add(I.Directory); - else - seenNames.Add(I.Name); - } - foreach (var I in pathsToRemove) - Config.InstancePaths.Remove(I); - } - - /// - /// Unlocks a acquired with - /// - /// The to unlock - void UnlockLoggingID(byte ID) - { - lock (UsedLoggingIDs) - { - UsedLoggingIDs.Remove(ID); - } - } - - /// - /// Gets and locks a - /// - /// A logging ID for the must be released using - byte LockLoggingID() - { - lock (UsedLoggingIDs) - { - for (byte I = 1; I < 100; ++I) - if (!UsedLoggingIDs.Contains(I)) - { - UsedLoggingIDs.Add(I); - return I; - } - } - throw new Exception("All logging IDs in use!"); - } - - /// - /// Creates and starts a for a at - /// - /// The for the - /// The inactive on success, on failure - ServiceHost SetupInstance(IInstanceConfig config) - { - Instance instance; - string instanceName; - try - { - if (hosts.ContainsKey(config.Directory)) - { - var datInstance = ((Instance)hosts[config.Directory].SingletonInstance); - Logger.WriteError(String.Format("Unable to start instance at path {0}. Has the same name as instance at path {1}. Detaching...", config.Directory, datInstance.ServerDirectory()), EventID.InstanceInitializationFailure, LoggingID); - return null; - } - if (!config.Enabled) - return null; - var ID = LockLoggingID(); - Logger.WriteInfo(String.Format("Instance {0} ({1}) assigned logging ID {2}", config.Name, config.Directory, ID), EventID.InstanceIDAssigned, ID); - instanceName = config.Name; - instance = new Instance(config, ID); - } - catch (Exception e) - { - Logger.WriteError(String.Format("Unable to start instance at path {0}. Detaching... Error: {1}", config.Directory, e.ToString()), EventID.InstanceInitializationFailure, LoggingID); - return null; - } - - var host = CreateHost(instance, String.Format("{0}/{1}", Definitions.InstanceInterfaceName, instanceName)); - hosts.Add(instanceName, host); - - AddEndpoint(host, typeof(ITGConnectivity)); - AddEndpoint(host, typeof(ITGAdministration)); - AddEndpoint(host, typeof(ITGByond)); - AddEndpoint(host, typeof(ITGChat)); - AddEndpoint(host, typeof(ITGCompiler)); - AddEndpoint(host, typeof(ITGConfig)); - AddEndpoint(host, typeof(ITGDreamDaemon)); - AddEndpoint(host, typeof(ITGInstance)); - AddEndpoint(host, typeof(ITGInterop)); - AddEndpoint(host, typeof(ITGRepository)); - - host.Authorization.ServiceAuthorizationManager = instance; - host.Authentication.ServiceAuthenticationManager = new AuthenticationHeaderDecoder(); - return host; - } - - /// - /// Adds a WCF endpoint for a component - /// - /// The service host to add the component to - /// The type of the component - void AddEndpoint(ServiceHost host, Type typetype) - { - var bindingName = typetype.Name; - host.AddServiceEndpoint(typetype, new NetNamedPipeBinding() { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = Definitions.TransferLimitLocal }, bindingName); - var httpsBinding = new BasicHttpsBinding() - { - SendTimeout = new TimeSpan(0, 0, 40), - MaxReceivedMessageSize = Definitions.TransferLimitRemote - }; - var requireAuth = typetype.Name != typeof(ITGConnectivity).Name; - host.AddServiceEndpoint(typetype, httpsBinding, bindingName); - } - - /// - /// Shuts down all active s and calls on it's - /// - void OnStop() - { - lock (this) - { - try - { - foreach (var I in hosts) - { - var host = I.Value; - var instance = (Instance)host.SingletonInstance; - host.Close(); - instance.Dispose(); - UnlockLoggingID(instance.LoggingID); - } - } - catch (Exception e) - { - Logger.WriteError(e.ToString(), EventID.ServiceShutdownFail, LoggingID); - } - serviceHost.Close(); - } - Config.Save(DefaultConfigDirectory); - } - - /// - public void VerifyConnection() { } - - /// - public void PrepareForUpdate() - { - foreach (var I in hosts) - ((Instance)I.Value.SingletonInstance).Reattach(false); - } - - /// - public ushort RemoteAccessPort() - { - return Config.RemoteAccessPort; - } - - /// - public string SetRemoteAccessPort(ushort port) - { - if (port == 0) - return "Cannot bind to port 0"; - Config.RemoteAccessPort = port; - return null; - } - - /// - public string Version() - { - return VersionString; - } - - /// - public bool SetPythonPath(string path) - { - if (!Directory.Exists(path)) - return false; - Config.PythonPath = Path.GetFullPath(path); - return true; - } - - /// - public string PythonPath() - { - return Config.PythonPath; - } - - /// - public IList ListInstances() - { - var result = new List(); - lock (this) - foreach (var ic in GetInstanceConfigs()) - result.Add(new InstanceMetadata - { - Name = ic.Name, - Path = ic.Directory, - Enabled = ic.Enabled, - LoggingID = (byte)(ic.Enabled ? ((Instance)hosts[ic.Name].SingletonInstance).LoggingID : 0) - }); - return result; - } - - /// - public string CreateInstance(string Name, string path) - { - path = Helpers.NormalizePath(path); - var res = CheckInstanceName(Name); - if (res != null) - return res; - if (File.Exists(path) || Directory.Exists(path)) - return "Cannot create instance at pre-existing path!"; - lock (this) - { - if (Config.InstancePaths.Contains(path)) - return String.Format("Instance at {0} already exists!", path); - foreach (var oic in GetInstanceConfigs()) - if (Name == oic.Name) - return String.Format("Instance named {0} already exists!", oic.Name); - IInstanceConfig ic; - try - { - ic = new InstanceConfig(path) - { - Name = Name - }; - Directory.CreateDirectory(path); - ic.Save(); - Config.InstancePaths.Add(path); - } - catch (Exception e) - { - return e.ToString(); - } - return SetupOneInstance(ic); - } - } - - /// - /// Starts and onlines an instance located at - /// - /// The for the - /// on success, error message on failure - string SetupOneInstance(IInstanceConfig config) - { - if (!config.Enabled) - return null; - try - { - var host = SetupInstance(config); - if (host != null) - host.Open(); - else - lock (this) - Config.InstancePaths.Remove(config.Directory); - return null; - } - catch (Exception e) - { - return "Instance set up but an error occurred while starting it: " + e.ToString(); - } - } - - /// - public string ImportInstance(string path) - { - path = Helpers.NormalizePath(path); - lock (this) - { - if (Config.InstancePaths.Contains(path)) - return String.Format("Instance at {0} already exists!", path); - if(!Directory.Exists(path)) - return String.Format("There is no instance located at {0}!", path); - IInstanceConfig ic; - try - { - ic = InstanceConfig.Load(path); - foreach(var oic in GetInstanceConfigs()) - if(ic.Name == oic.Name) - return String.Format("Instance named {0} already exists!", oic.Name); - ic.Save(); - Config.InstancePaths.Add(path); - } - catch (Exception e) - { - return e.ToString(); - } - return SetupOneInstance(ic); - } - } - - /// - public bool InstanceEnabled(string Name) - { - lock(this) - { - return hosts.ContainsKey(Name); - } - } - - /// - public string SetInstanceEnabled(string Name, bool enabled) - { - return SetInstanceEnabledImpl(Name, enabled, out string path); - } - - - /// - /// Sets a 's enabled status - /// - /// The whom's status should be changed - /// to enable the , to disable it - /// The path to the modified - /// on success, error message on failure - string SetInstanceEnabledImpl(string Name, bool enabled, out string path) - { - path = null; - lock (this) - { - var hostIsOnline = hosts.ContainsKey(Name); - if (enabled) - { - if (hostIsOnline) - return null; - foreach (var ic in GetInstanceConfigs()) - if (ic.Name == Name) - { - path = ic.Directory; - ic.Enabled = true; - ic.Save(); - return SetupOneInstance(ic); - } - return String.Format("Instance {0} does not exist!", Name); - } - else - { - if (!hostIsOnline) - return null; - var host = hosts[Name]; - hosts.Remove(Name); - var inst = (Instance)host.SingletonInstance; - host.Close(); - path = inst.ServerDirectory(); - inst.Offline(); - inst.Dispose(); - UnlockLoggingID(inst.LoggingID); - return null; - } - } - } - - /// - public string RenameInstance(string name, string new_name) - { - if (name == new_name) - return null; - var res = CheckInstanceName(new_name); - if (res != null) - return res; - lock (this) - { - //we have to check em all anyway - IInstanceConfig the_droid_were_looking_for = null; - foreach (var ic in GetInstanceConfigs()) - if (ic.Name == name) - { - the_droid_were_looking_for = ic; - break; - } - else if (ic.Name == new_name) - return String.Format("There is already another instance named {0}!", new_name); - if (the_droid_were_looking_for == null) - return String.Format("There is no instance named {0}!", name); - var ie = InstanceEnabled(name); - if(ie) - SetInstanceEnabled(name, false); - the_droid_were_looking_for.Name = new_name; - string result = ""; - try - { - the_droid_were_looking_for.Save(); - result = null; - } - catch(Exception e) - { - result = "Could not save instance config! Error: " + e.ToString(); - } - finally - { - if (ie) - { - var resRestore = SetInstanceEnabled(new_name, true); - if (resRestore != null) - result = (result + " " + resRestore).Trim(); - } - } - return result; - } - } - - /// - public string DetachInstance(string name) - { - lock (this) - { - var res = SetInstanceEnabledImpl(name, false, out string path); - if (res != null) - return res; - if (path == null) //gotta find it ourselves - foreach (var ic in GetInstanceConfigs()) - if (ic.Name == name) - { - path = ic.Directory; - break; - } - if (path == null) - return String.Format("No instance named {0} exists!", name); - Config.InstancePaths.Remove(path); - return null; - } - } - - #region IDisposable Support - /// - /// To detect redundant calls - /// - private bool disposedValue = false; - - /// - /// Implements the pattern. Calls - /// - /// if was called manually, if it was from the finalizer - void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - OnStop(); - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~Server() { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - /// - /// Implements the pattern - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - } -} diff --git a/legacy/V3/TGS.Server/ServerConfig.cs b/legacy/V3/TGS.Server/ServerConfig.cs deleted file mode 100644 index cd1ea22fa9..0000000000 --- a/legacy/V3/TGS.Server/ServerConfig.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; -using System.IO; - -namespace TGS.Server -{ - /// - /// Class for storing server wide settings - /// - public sealed class ServerConfig - { - /// - /// The filename to save the as - /// - [JsonIgnore] - const string JSONFilename = "ServerConfig.json"; - /// - /// The most recent version of the config file - /// - [JsonIgnore] - const ulong CurrentVersion = 8; - - /// - /// The version of the - /// - public ulong Version { get; private set; } = CurrentVersion; - - /// - /// List of paths that contain s - /// - public List InstancePaths { get; private set; } = new List(); - - /// - /// Port used to access the remotely - /// - public ushort RemoteAccessPort { get; set; } = 38607; - - /// - /// Path to the directory containing the Python2.7 installation - /// - public string PythonPath { get; set; } = "C:\\Python27"; - - /// - /// Saves the to a target - /// - /// The directory in which to save the - public void Save(string directory) - { - var data = JsonConvert.SerializeObject(this, Formatting.Indented); - var path = Path.Combine(directory, JSONFilename); - File.WriteAllText(path, data); - } - - /// - /// Load a from a given - /// - /// The directory containing the - /// The loaded - public static ServerConfig Load(string directory) - { - var configtext = File.ReadAllText(Path.Combine(directory, JSONFilename)); - return JsonConvert.DeserializeObject(configtext); - } - } -} diff --git a/legacy/V3/TGS.Server/TGS.Server.csproj b/legacy/V3/TGS.Server/TGS.Server.csproj deleted file mode 100644 index 83f970c8c1..0000000000 --- a/legacy/V3/TGS.Server/TGS.Server.csproj +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - Debug - AnyCPU - {F32EDA25-0855-411C-AF5E-F0D042917E2D} - Library - TGS.Server - TGS.Server - v4.5.2 - 512 - true - - - - - - tgs.ico - - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\ - TRACE - bin\x86\Release\TGS.Server.xml - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - false - - - - - - - ..\packages\Discord.Net.Core.1.0.2\lib\net45\Discord.Net.Core.dll - - - ..\packages\Discord.Net.Rest.1.0.2\lib\net45\Discord.Net.Rest.dll - - - ..\packages\Discord.Net.WebSocket.1.0.2\lib\net45\Discord.Net.WebSocket.dll - - - ..\packages\LibGit2Sharp-SSH.1.0.22\lib\net40\LibGit2Sharp.dll - - - ..\packages\SmartIrc4net.1.1\lib\Meebey.SmartIrc4net.dll - - - ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - - - ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - - - - - - - ..\packages\System.Interactive.Async.3.1.1\lib\net45\System.Interactive.Async.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - Designer - - - - - {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} - TGS.Interface - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Server/packages.config b/legacy/V3/TGS.Server/packages.config deleted file mode 100644 index 9d3af3538f..0000000000 --- a/legacy/V3/TGS.Server/packages.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Server/tgs.ico b/legacy/V3/TGS.Server/tgs.ico deleted file mode 100644 index 6ed69fadfcdf8deb049f66dfffc46d5361332feb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123198 zcmeHQ2b>f|_HG1(QxxyiJH1mu&xnDbAQ>bqf<%dugM>v;q9P!>AR-bJ0TGd8IK_aX z2#5hz#f;%jFx}~`r~lK_Logl)QvdI(nVPNX?V0K7shQnbrhdPk?y0V>diCn-SFc{Z zs&<@mPDSUKV;r2TJ0mJMP7lX%PB zX4{+n+bBCg5VM}LHT8#F$viu1R(UX^)tZ+V_Gx?kJFQ0BSK&->jo zecN3mM)kc^4DH@rEW2%nSTJ*<7}B+=xUyR_F|boZaasN9&s zh;pEBOrtZae1USrH3Qp=F@rjaZ~pUd@%5MgC$2(XU7MdHx?WO4^lDQ}4DZoGG&=Lh zFD||4PhcZ@>PM_~5;_BpuAV zX^pCa&+yq^IW>AHEX@4(t~{e7{E={PkDy%f5YL+>p-V z=E)-kcp&&p&(<|hYH`8w%fY7uqx!a$&pfx`(a`hXe+Tl4!vB8$NsR2%Myy_P3+mTF z@EJY}IzDB+pIEa}CVuv*N1~qpcGuTp@hy|Z^;ca1`kRQ4Klqzi|Hx{#3G47_t6G1! zsotp-53=qI&&7SdsrcY;Z^=6F89v+Q!aokh_5S?vgFT;@k@%7H_iim7dtjNEdEGF< zXZURE+9%vhoDAsHKz#c555)5u!}a&z-`K&O#KM~=$@=jbKHI**sTVapr|MSLXKvm# zV$QUy#o)`E2-SY$(0=b|?Kh%VOVQ}8D&T+Bt-#Vnt!kg(bZTA6OsOvHqVc1Fuzh z{sHB#0?vkYYJA42mz;O3ELZ;l9-N#yCsqPKX5n4?>XP%1U5oP8qOM;7Z?n)|l@O;t z;uKbLLOVhJ*Zn|!G1+-^WT0Wa7P(}^v+^9;2hNn$Ko-4qU>EQ3(;Ne%~DG%`I zYs4+*J*Mf<@YHDoJ2$M-&n4g7W!*M&qEiig1AYcVZtMk?iD#K7+f4nhTRjx7{xdCP z*FZjd>x}X4Y~}xX)5kfLaW$}0gY$utop?te_ZkS)N1)9`>pCY&oZmWQoQxaLv4MPd z`n9ho+Fe|Y*xEUuWBv0FM;^>eg_U>Ac0T^_y>h+V);SeqD&JjGW9&nkMb^QqW?m=#R0PIhM>^pVG+pm|qYkr^7X0JG9XQ?-YgR6j@LeZ@SP_Z4kwIpARtbg^iE>Mf@GIRE3>?eG_yaE9TrVTJoxHwF;CmKcd|l zUD2ktYzDN?7+~Pni>j3uQ?KqTAN}vw#q(Er>Svih+MoU5v@!h!^I%@>Ep=EXdjH~P37$j0w4Mw{S$5U&($PDp=L{=`4< zdHYS*iq~Iy4tbm^`OiGNwKz8idR`0Q^9$9+khfCSke7bnFZP{$Z~8z-RPvU2GVd3k z+i)0k)SOToRHB|JmMxee=H-u}o*=G*?xf_y%Uhoo4Iyv5K?ikO)I8n+*6N~8pLT9?hL{YUitorpr<9lSLdmyJ|Ng$rgE9^Mi>G`;-}7nD zoSJnZ`_NA{mhu5I*4elBf93EV-i^Lm>Z3CzUMY3AJLXIkKmG8%WI1uO`H3}R`h+23 z9CRXGK2ygdO_yA7+}-WBS5Sfd~#}4j@qmx*% zXqLEf;!yN6^(EffztsP8C1QR*^Kk1DETivbb)6xXH#ru38wI|vY<}+VUPYVB_m1x) z56V{_MgJ?3=AXdtSHZ89$a~b_E{%_cE~UsWcE0D%xz6j`o-c>GABE?}W6bdbvBd*z z%ew7X{~Mfs#1Fv9_~AWU9<}MQzm!`zYf`cNVnqN)4#o}@(Kk&;-}(*ut98IxE_AS5 z=vwR0R^NaR)6t(*#P}yxHjbDsoV`vpdF*kjI{DC++(*oZwHpOad51@#Q%N4o^PrO_ z$9vV0t~ri~9)*FFB{pip4h{KIQ7Zai2B0 zIkI;fnH=N3Gogn!zj{FX+IY`S2A*m{p3Z~*`n76LwB>(+uZ4ZuU09Q8n3m7*S!Oc4 zXA6^}Wm@2$xUG)*y@B@Jhqm4YJ*E(4eFJPARAs9(Fm(|9`!`I(w0wro0=LyMMm8#c z(#W39a~szl2Hej>zJfBCvY+p9eICFxuc=S+;C1v7O3P=V56pXJ{lkY{JAzh(#NneI z?>NvUocnY|6wQP0oq$Uv}!TNtW(5;&|z-8cBs^+IIf0l z6yJZh8)L}3#ge%RFTlmi=qGaX#|=9E>i(BSSkb=WY0&Q*4u;%{!WH%9+$&niI@dnQk-GVT{re-` z?E4sdQ~xJz>m);mSK^xRY8{l9J()JwO&S|A`>#f2w_^nLanqdE zw87H(d6FNhe8{Vrs_e<6FUxHgjAoEyB$KXBlH9K-C}yH^}McrcQl z7fa{m$?~ro(L+4_#DjADp~`1jEOf%K%VqyKv|F>YATQoiyeEFpmqqf6RemIa$Hmfl z(<0@+ymb@D9+9?XSeKc@=lZL99RvKXQFZ4#NBbDZWGW<6K6UUtK89)cABkIZ1sJ;E-cz+I`e} z$TC@W$Hu4Mt$0saP8mbnpdJ#H&oK_i&unA7-+0H4kk`v^pNa9wW;_x3Gf0PZ( zQ`M1Wb!`0Sxh(tjm!7TIw(iMm!g~49KbSXcscnHP2lFPfZ1ppJ{168^U3t8h_mTh9 z88}|gy`r@oi&Fp08#^$neD=GX%b;9=&PF@DhyFEH&d-oW=Dp?Vb>*i|xKb4xb%xGE z8*X2`p9iQ*!Oke4tBLv3$4R}C??vG!A$tt0q5u2^_9u?Zsf+6R zCv|t^eSmqhj93A7DB2uPhiv>8vC4Mf!E4qH{kG)A;@Oi0=O$n~mNs5qvrNvJP#4$r zZJmAl93{^pak!8E?|b0!dg%K#t{Kq&xA!l*wfwxi#N|c^&@RAZr+dqDDlh zXn4kv`yoTVM4NmGT_R#zK>YWJ8x0#^1;}+Rr{I^cH*kDV0lNMT$os#L-J3Dq&BYvO zEzDolLRq<&HK@KBH2$|$?LXYmw|%_|u(A6pQ0%S7*%NWlaqhSRF%U<@PH}qjb_!${H(*ZLS$NZR6(C4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl - /// Creates a mock that returns a specific when that component is requested - ///
- /// The the resulting should return - /// A mock - protected IInstance MockInterfaceToRepo(ITGRepository repo) - { - var mock = new Mock(); - mock.Setup(foo => foo.Repository).Returns(repo); - return mock.Object; - } - } -} diff --git a/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs b/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs deleted file mode 100644 index a5f2b72ede..0000000000 --- a/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using System.Collections.Generic; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.CommandLine.Commands.Repository.Tests -{ - /// - /// Tests for - /// - [TestClass] - public class TestRepoSetPushTestmergeCommitsCommand : RepositoryCommandTest - { - /// - /// Ensure it can be turned on - /// - [TestMethod] - public void TestTurnOn() - { - var ran = false; - var repo = new Mock(); - repo.Setup(foo => foo.SetPushTestmergeCommits(true)).Callback(() => { ran = true; }); - ConsoleCommand.Instance = MockInterfaceToRepo(repo.Object); - Assert.AreEqual(new RepoSetPushTestmergeCommitsCommand().DoRun(new List { "on" }), Command.ExitCode.Normal); - Assert.IsTrue(ran); - } - - /// - /// Ensure that it can be turned off - /// - [TestMethod] - public void TestTurnOff() - { - - var ran = false; - var repo = new Mock(); - repo.Setup(foo => foo.SetPushTestmergeCommits(false)).Callback(() => { ran = true; }); - ConsoleCommand.Instance = MockInterfaceToRepo(repo.Object); - Assert.AreEqual(new RepoSetPushTestmergeCommitsCommand().DoRun(new List { "off" }), Command.ExitCode.Normal); - Assert.IsTrue(ran); - } - - /// - /// Ensure that is returned for gibberish parameters - /// - [TestMethod] - public void TestGibberishParam() - { - Assert.AreEqual(new RepoSetPushTestmergeCommitsCommand().DoRun(new List { "gibberish" }), Command.ExitCode.BadCommand); - } - } -} diff --git a/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs b/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs deleted file mode 100644 index f1aea5ce1d..0000000000 --- a/legacy/V3/TGS.Tests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using System.Collections.Generic; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.CommandLine.Commands.Repository.Tests -{ - /// - /// Tests for - /// - [TestClass] - public class TestRepoStatusCommand : RepositoryCommandTest - { - /// - /// Set the default returns for the mock to be - /// - /// A of with default return values - Mock GetDefaultMock() - { - var mock = new Mock(); - mock.SetReturnsDefault(null); - return mock; - } - - /// - /// Tests the results of - /// - [TestMethod] - public void TestPushTestmergeCommits() - { - var ran = false; - var mock = GetDefaultMock(); - mock.Setup(foo => foo.PushTestmergeCommits()).Returns(false).Callback(() => ran = true); - ConsoleCommand.Instance = MockInterfaceToRepo(mock.Object); - Assert.AreEqual(new RepoStatusCommand().DoRun(new List { }), Command.ExitCode.Normal); - Assert.IsTrue(ran); - - ran = false; - mock.Setup(foo => foo.PushTestmergeCommits()).Returns(true).Callback(() => ran = true); - ConsoleCommand.Instance = MockInterfaceToRepo(mock.Object); - Assert.AreEqual(new RepoStatusCommand().DoRun(new List { }), Command.ExitCode.Normal); - Assert.IsTrue(ran); - } - } -} diff --git a/legacy/V3/TGS.Tests/ControlPanel/TestInstanceSelector.cs b/legacy/V3/TGS.Tests/ControlPanel/TestInstanceSelector.cs deleted file mode 100644 index 2d65a3b1db..0000000000 --- a/legacy/V3/TGS.Tests/ControlPanel/TestInstanceSelector.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using System.Collections.Generic; -using TGS.Interface; -using TGS.Interface.Components; - -namespace TGS.ControlPanel.Tests -{ - /// - /// Tests for - /// - [TestClass] - public class TestInstanceSelector - { - /// - /// Test that an can be created without issue - /// - [TestMethod] - public void TestInstatiation() - { - var mockLanding = new Mock(); - mockLanding.Setup(x => x.ListInstances()).Returns(new List()); - var mockInter = new Mock(); - - new InstanceSelector(mockInter.Object).Dispose(); - } - } -} diff --git a/legacy/V3/TGS.Tests/OutputProcOverriderTest.cs b/legacy/V3/TGS.Tests/OutputProcOverriderTest.cs deleted file mode 100644 index f73a473e0a..0000000000 --- a/legacy/V3/TGS.Tests/OutputProcOverriderTest.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TGS.Interface; - -namespace TGServiceTests -{ - /// - /// For tests that need to override - /// - public abstract class OutputProcOverriderTest - { - /// - /// Set to - /// - [TestInitialize] - public void Setup() - { - Command.OutputProcVar.Value = OutputProc; - } - - /// - /// Whe - /// - /// - protected virtual void OutputProc(string message) { } - } -} diff --git a/legacy/V3/TGS.Tests/Properties/AssemblyInfo.cs b/legacy/V3/TGS.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 4bde2505b7..0000000000 --- a/legacy/V3/TGS.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("TGStation Server Test Suite")] -[assembly: AssemblyDescription("Unit tests for the TGStation Server suite")] - -[assembly: ComVisible(false)] - -[assembly: Guid("fb693ffb-17e3-4e84-8cbf-6ffa9c8fd971")] diff --git a/legacy/V3/TGS.Tests/Server/TestHelpers.cs b/legacy/V3/TGS.Tests/Server/TestHelpers.cs deleted file mode 100644 index b9cf624171..0000000000 --- a/legacy/V3/TGS.Tests/Server/TestHelpers.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.IO; -using System.Linq; -using TGServiceTests; - -namespace TGS.Server.Tests -{ - /// - /// Tests for - /// - [TestClass] - public sealed class TestHelpers : TempDirectoryRequiredTest - { - [TestMethod] - public void TestGetFilesWithExtensionInDirectory() - { - Assert.ThrowsException(() => Helpers.GetFilesWithExtensionInDirectory(null, null).ToList()); - Assert.ThrowsException(() => Helpers.GetFilesWithExtensionInDirectory("", null).ToList()); - Assert.ThrowsException(() => Helpers.GetFilesWithExtensionInDirectory(null, "").ToList()); - - Assert.AreEqual(0, Helpers.GetFilesWithExtensionInDirectory("Z:/", "dm").Count()); - - Assert.AreEqual(0, Helpers.GetFilesWithExtensionInDirectory(TempPath, "dm").Count()); - - File.WriteAllText(Path.Combine(TempPath, "somefile.txt"), "asdf"); - - Assert.AreEqual(0, Helpers.GetFilesWithExtensionInDirectory(TempPath, "dm").Count()); - - File.WriteAllText(Path.Combine(TempPath, "somefile.dm"), "asdf"); - - Assert.AreEqual(1, Helpers.GetFilesWithExtensionInDirectory(TempPath, "dm").Count()); - } - } -} diff --git a/legacy/V3/TGS.Tests/ServiceInterface/TestHelpers.cs b/legacy/V3/TGS.Tests/ServiceInterface/TestHelpers.cs deleted file mode 100644 index 2742f1e854..0000000000 --- a/legacy/V3/TGS.Tests/ServiceInterface/TestHelpers.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace TGS.Interface.Tests -{ - /// - /// Tests for - /// - [TestClass] - public class TestHelpers - { - /// - /// Sample cleartext - /// - const string PlainText = "According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off the ground. The bee, of course, flies anyway because bees don't care what humans think is impossible."; - - /// - /// Run assertions for a successful call to - /// - /// The out string for the entropy parameter of - /// The result of with as a parameter - string AssertEncryptData(out string entropy) - { - var result = Helpers.EncryptData(PlainText, out entropy); - Assert.AreNotEqual(PlainText, entropy); - Assert.AreNotEqual(PlainText, result); - Assert.AreNotEqual(result, entropy); - Assert.IsFalse(String.IsNullOrWhiteSpace(result)); - Assert.IsFalse(String.IsNullOrWhiteSpace(entropy)); - return result; - } - - /// - /// Tests that can execute successfully - /// - [TestMethod] - public void TestEncryptDataWorks() - { - AssertEncryptData(out string entropy); - } - - /// - /// Tests that can execute successfully - /// - [TestMethod] - public void TestDecryptDataWorks() - { - var result = AssertEncryptData(out string entropy); - var decrypted = Helpers.DecryptData(result, entropy); - Assert.AreEqual(decrypted, PlainText); - } - } -} diff --git a/legacy/V3/TGS.Tests/ServiceInterface/TestInterface.cs b/legacy/V3/TGS.Tests/ServiceInterface/TestInterface.cs deleted file mode 100644 index 1b51fa2b50..0000000000 --- a/legacy/V3/TGS.Tests/ServiceInterface/TestInterface.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Net; -using System.ServiceModel; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TGS.Interface.Components; - -namespace TGS.Interface.Tests -{ - /// - /// Tests for - /// - [TestClass] - public class TestInterface - { - /// - /// Name to use for testing s - /// - const string TestInstanceName = "TestInstance"; - /// - /// Test that can execute successfully - /// - [TestMethod] - public void TestSetBadCertificateHandler() - { - Func func = (message) => - { - Assert.IsFalse(String.IsNullOrWhiteSpace(message)); - return true; - }; - Client.SetBadCertificateHandler(func); - } - - /// - /// Test that properly sets - /// - [TestMethod] - public void TestBadCertificateHandler() - { - var ran = false; - Client.SetBadCertificateHandler(_ => - { - ran = true; - return true; - }); - ServicePointManager.ServerCertificateValidationCallback(this, new System.Security.Cryptography.X509Certificates.X509Certificate(), new System.Security.Cryptography.X509Certificates.X509Chain(), System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors); - Assert.IsTrue(ran); - } - - /// - /// Creates a remote configured pointing at an invalid address - /// - /// The created - Client CreateFakeRemoteInterface() - { - return new Client(new RemoteLoginInfo("some.fake.url.420", 34752, "user", "password")); - } - - /// - /// Test that can execute successfully and creates a local connection - /// - [TestMethod] - public void TestLocalInstantiation() - { - Assert.IsFalse(new Client().IsRemoteConnection); - } - - /// - /// Test that can execute successfully - /// - [TestMethod] - public void TestRemoteInstatiation() - { - Assert.IsTrue(CreateFakeRemoteInterface().IsRemoteConnection); - } - - [TestMethod] - public void TestCopyRemoteInterface() - { - var first = CreateFakeRemoteInterface(); - var second = new Client(first.LoginInfo); - Assert.AreEqual(first.LoginInfo.IP, second.LoginInfo.IP); - Assert.AreEqual(first.LoginInfo.Port, second.LoginInfo.Port); - Assert.AreEqual(first.LoginInfo.Username, second.LoginInfo.Username); - Assert.AreEqual(first.LoginInfo.Password, second.LoginInfo.Password); - Assert.IsTrue(second.IsRemoteConnection); - } - - [TestMethod] - public void TestLocalAccessInterfaceAllowsWindowsImpersonation() - { - var inter = new Client(); - var po = new PrivateObject(inter); - var cf = (ChannelFactory)po.Invoke("CreateChannel", new Type[] { typeof(string) }, new object[] { TestInstanceName }, new Type[] { typeof(ITGConfig) }); - Assert.AreEqual(cf.Credentials.Windows.AllowedImpersonationLevel, System.Security.Principal.TokenImpersonationLevel.Impersonation); - } - } -} diff --git a/legacy/V3/TGS.Tests/TGS.Tests.csproj b/legacy/V3/TGS.Tests/TGS.Tests.csproj deleted file mode 100644 index df98e93aeb..0000000000 --- a/legacy/V3/TGS.Tests/TGS.Tests.csproj +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Debug - AnyCPU - {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971} - Library - Properties - TGS.Tests - TGS.Tests - v4.6.1 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - false - - - - ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll - - - ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - ..\packages\Moq.4.7.145\lib\net45\Moq.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - {89191f69-b18e-4b59-b72e-e12f9b6811a0} - TGCommandLine - - - {394e7643-6b8c-416f-ab18-95ac12648cdc} - TGControlPanel - - - {8956d4c3-bfb9-448e-bf5f-ee7e6f9996f9} - TGInstallerWrapper - - - {f32eda25-0855-411c-af5e-f0d042917e2d} - TGServerService - - - {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} - TGServiceInterface - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS.Tests/TempDirectoryRequiredTest.cs b/legacy/V3/TGS.Tests/TempDirectoryRequiredTest.cs deleted file mode 100644 index b18858ccff..0000000000 --- a/legacy/V3/TGS.Tests/TempDirectoryRequiredTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.IO; - -namespace TGServiceTests -{ - /// - /// To be the parent of test classes that required a temporary directory - /// - [TestClass] - public abstract class TempDirectoryRequiredTest - { - /// - /// The path to the temporary directory - /// - protected string TempPath; - - /// - /// Construct a - /// - protected TempDirectoryRequiredTest() { } - - /// - /// Setup - /// - [TestInitialize] - public void Setup() - { - TempPath = Path.GetTempFileName(); - File.Delete(TempPath); - Directory.CreateDirectory(TempPath); - } - - /// - /// Cleanup - /// - [TestCleanup] - public void Cleanup() - { - Directory.Delete(TempPath, true); - } - } -} diff --git a/legacy/V3/TGS.Tests/packages.config b/legacy/V3/TGS.Tests/packages.config deleted file mode 100644 index 968620fd09..0000000000 --- a/legacy/V3/TGS.Tests/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/legacy/V3/TGS3.json b/legacy/V3/TGS3.json deleted file mode 100644 index a6e21bf294..0000000000 --- a/legacy/V3/TGS3.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "documentation": [ - "Place this file in the root of your repo in order to configure TGS3 per repository options", - "This file is not required, it merely configures changelog generation, static directories, and .dlls", - "All keys except the ones below are ignored by the service" - ], - "changelog": { - "script": "path/to/changelog/generator.py", - "arguments": "space delimited \"script arguments\"", - "pip_dependancies": [ - "optional", - "list", - "of", - "pip", - "install", - "packages" - ] - }, - "synchronize_paths": [ - "list", - "of", - "regex", - "paths", - "to", - "stage", - "for", - "git", - "commit" - ], - "static_directories": [ - "list", - "of", - "directories", - "that", - "should", - "not", - "be", - "changed", - "with", - "repo", - "updates", - "et al" - ], - "dlls": [ - "list", - "of", - ".dlls", - "that", - "your", - "game", - "uses", - "these", - "will", - "be", - "handled", - "properly" - ] -} \ No newline at end of file diff --git a/legacy/V3/TGStationServer3.sln b/legacy/V3/TGStationServer3.sln deleted file mode 100644 index 8b72b2546b..0000000000 --- a/legacy/V3/TGStationServer3.sln +++ /dev/null @@ -1,188 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.16 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.Server", "TGS.Server\TGS.Server.csproj", "{F32EDA25-0855-411C-AF5E-F0D042917E2D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.ControlPanel", "TGS.ControlPanel\TGS.ControlPanel.csproj", "{394E7643-6B8C-416F-AB18-95AC12648CDC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.CommandLine", "TGS.CommandLine\TGS.CommandLine.csproj", "{89191F69-B18E-4B59-B72E-E12F9B6811A0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.Interface", "TGS.Interface\TGS.Interface.csproj", "{AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}" -EndProject -Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "TGS.Installer", "TGS.Installer\TGS.Installer.wixproj", "{154435F6-0890-42D4-9AEC-B743D4FBC1CB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2D8FC6AA-1D33-44B6-81C2-35ED7A87EDBC}" - ProjectSection(SolutionItems) = preProject - .codecov.yml = .codecov.yml - .gitignore = .gitignore - .travis.yml = .travis.yml - appveyor.yml = appveyor.yml - AssemblyInfo.global.cs = AssemblyInfo.global.cs - README.md = README.md - tgs.ico = tgs.ico - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.Installer.UI", "TGS.Installer.UI\TGS.Installer.UI.csproj", "{8956D4C3-BFB9-448E-BF5F-EE7E6F9996F9}" - ProjectSection(ProjectDependencies) = postProject - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB} = {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB} - {154435F6-0890-42D4-9AEC-B743D4FBC1CB} = {154435F6-0890-42D4-9AEC-B743D4FBC1CB} - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "legacy", "legacy", "{0DDF886A-7ABB-4DCA-96A3-349B2DA00016}" - ProjectSection(SolutionItems) = preProject - legacy\config.bat = legacy\config.bat - legacy\copyexclude.txt = legacy\copyexclude.txt - legacy\disable crash dialog.reg = legacy\disable crash dialog.reg - legacy\Fix Errors.bat = legacy\Fix Errors.bat - legacy\install.bat = legacy\install.bat - legacy\Merge pr without updating.bat = legacy\Merge pr without updating.bat - legacy\OnServerBoot.bat = legacy\OnServerBoot.bat - legacy\README.md = legacy\README.md - legacy\Recompile.bat = legacy\Recompile.bat - legacy\Reset and Recompile.bat = legacy\Reset and Recompile.bat - legacy\Start Bot.bat = legacy\Start Bot.bat - legacy\Start Server.bat = legacy\Start Server.bat - legacy\Update Server.bat = legacy\Update Server.bat - legacy\Update to PR.bat = legacy\Update to PR.bat - legacy\Update without resetting.bat = legacy\Update without resetting.bat - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bin", "bin", "{3BB3DA66-970C-4AAE-A77C-9E40F5E7E56A}" - ProjectSection(SolutionItems) = preProject - legacy\bin\activepr.bat = legacy\bin\activepr.bat - legacy\bin\build.bat = legacy\bin\build.bat - legacy\bin\copyfromgit.bat = legacy\bin\copyfromgit.bat - legacy\bin\findab.bat = legacy\bin\findab.bat - legacy\bin\findbyond.bat = legacy\bin\findbyond.bat - legacy\bin\findgit.bat = legacy\bin\findgit.bat - legacy\bin\getcurdate.bat = legacy\bin\getcurdate.bat - legacy\bin\getunixtime.bat = legacy\bin\getunixtime.bat - legacy\bin\unixtime.vbs = legacy\bin\unixtime.vbs - legacy\bin\updategit.bat = legacy\bin\updategit.bat - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DMAPI", "DMAPI", "{9032B448-5E2B-4E71-8ECB-D5FB828E049C}" - ProjectSection(SolutionItems) = preProject - DMAPI\server_tools.dm = DMAPI\server_tools.dm - DMAPI\st_commands.dm = DMAPI\st_commands.dm - DMAPI\st_interface.dm = DMAPI\st_interface.dm - TGS3.json = TGS3.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{081BB0BB-2E84-47D4-9419-A2AB8E814ABF}" - ProjectSection(SolutionItems) = preProject - Tools\build_byond.sh = Tools\build_byond.sh - Tools\BuildDox.ps1 = Tools\BuildDox.ps1 - Tools\Config.dm = Tools\Config.dm - Tools\CoverageExclusions.runsettings = Tools\CoverageExclusions.runsettings - Tools\DMAPITravisTester.dme = Tools\DMAPITravisTester.dme - Tools\Doxyfile = Tools\Doxyfile - Tools\install_byond.sh = Tools\install_byond.sh - Tools\PostCIBuild.ps1 = Tools\PostCIBuild.ps1 - Tools\SignBasics.ps1 = Tools\SignBasics.ps1 - Tools\SignMSI.ps1 = Tools\SignMSI.ps1 - Tools\Test.dm = Tools\Test.dm - Tools\tgstation13.org.pfx = Tools\tgstation13.org.pfx - Tools\UploadCoverage.ps1 = Tools\UploadCoverage.ps1 - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{287B900C-1AFB-49B8-8BDD-C9058971C20C}" - ProjectSection(SolutionItems) = preProject - .github\CONTRIBUTING.md = .github\CONTRIBUTING.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.Interface.Bridge", "TGS.Interface.Bridge\TGS.Interface.Bridge.csproj", "{9A01EF03-8EAE-45CB-8B87-4A17BD904557}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.Tests", "TGS.Tests\TGS.Tests.csproj", "{FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.Server.Service", "TGS.Server.Service\TGS.Server.Service.csproj", "{3F81E398-B223-4006-B40C-C2800714CE29}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TGS.Server.Console", "TGS.Server.Console\TGS.Server.Console.csproj", "{509433F6-AEFB-44CA-BFE3-C782166D2CC3}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug-Client|Any CPU = Debug-Client|Any CPU - Release|Any CPU = Release|Any CPU - Release-Client|Any CPU = Release-Client|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F32EDA25-0855-411C-AF5E-F0D042917E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F32EDA25-0855-411C-AF5E-F0D042917E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F32EDA25-0855-411C-AF5E-F0D042917E2D}.Debug-Client|Any CPU.ActiveCfg = Debug|Any CPU - {F32EDA25-0855-411C-AF5E-F0D042917E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F32EDA25-0855-411C-AF5E-F0D042917E2D}.Release|Any CPU.Build.0 = Release|Any CPU - {F32EDA25-0855-411C-AF5E-F0D042917E2D}.Release-Client|Any CPU.ActiveCfg = Release|Any CPU - {394E7643-6B8C-416F-AB18-95AC12648CDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {394E7643-6B8C-416F-AB18-95AC12648CDC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {394E7643-6B8C-416F-AB18-95AC12648CDC}.Debug-Client|Any CPU.ActiveCfg = Debug|Any CPU - {394E7643-6B8C-416F-AB18-95AC12648CDC}.Debug-Client|Any CPU.Build.0 = Debug|Any CPU - {394E7643-6B8C-416F-AB18-95AC12648CDC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {394E7643-6B8C-416F-AB18-95AC12648CDC}.Release|Any CPU.Build.0 = Release|Any CPU - {394E7643-6B8C-416F-AB18-95AC12648CDC}.Release-Client|Any CPU.ActiveCfg = Release|Any CPU - {394E7643-6B8C-416F-AB18-95AC12648CDC}.Release-Client|Any CPU.Build.0 = Release|Any CPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0}.Debug-Client|Any CPU.ActiveCfg = Debug|Any CPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0}.Debug-Client|Any CPU.Build.0 = Debug|Any CPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0}.Release|Any CPU.Build.0 = Release|Any CPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0}.Release-Client|Any CPU.ActiveCfg = Release|Any CPU - {89191F69-B18E-4B59-B72E-E12F9B6811A0}.Release-Client|Any CPU.Build.0 = Release|Any CPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}.Debug-Client|Any CPU.ActiveCfg = Debug|Any CPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}.Debug-Client|Any CPU.Build.0 = Debug|Any CPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}.Release|Any CPU.Build.0 = Release|Any CPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}.Release-Client|Any CPU.ActiveCfg = Release|Any CPU - {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB}.Release-Client|Any CPU.Build.0 = Release|Any CPU - {154435F6-0890-42D4-9AEC-B743D4FBC1CB}.Debug|Any CPU.ActiveCfg = Release|x86 - {154435F6-0890-42D4-9AEC-B743D4FBC1CB}.Debug|Any CPU.Build.0 = Release|x86 - {154435F6-0890-42D4-9AEC-B743D4FBC1CB}.Debug-Client|Any CPU.ActiveCfg = Release|x86 - {154435F6-0890-42D4-9AEC-B743D4FBC1CB}.Release|Any CPU.ActiveCfg = Release|x86 - {154435F6-0890-42D4-9AEC-B743D4FBC1CB}.Release|Any CPU.Build.0 = Release|x86 - {154435F6-0890-42D4-9AEC-B743D4FBC1CB}.Release-Client|Any CPU.ActiveCfg = Release|x86 - {8956D4C3-BFB9-448E-BF5F-EE7E6F9996F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8956D4C3-BFB9-448E-BF5F-EE7E6F9996F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8956D4C3-BFB9-448E-BF5F-EE7E6F9996F9}.Debug-Client|Any CPU.ActiveCfg = Debug|Any CPU - {8956D4C3-BFB9-448E-BF5F-EE7E6F9996F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8956D4C3-BFB9-448E-BF5F-EE7E6F9996F9}.Release|Any CPU.Build.0 = Release|Any CPU - {8956D4C3-BFB9-448E-BF5F-EE7E6F9996F9}.Release-Client|Any CPU.ActiveCfg = Release|Any CPU - {9A01EF03-8EAE-45CB-8B87-4A17BD904557}.Debug|Any CPU.ActiveCfg = Debug|x86 - {9A01EF03-8EAE-45CB-8B87-4A17BD904557}.Debug|Any CPU.Build.0 = Debug|x86 - {9A01EF03-8EAE-45CB-8B87-4A17BD904557}.Debug-Client|Any CPU.ActiveCfg = Debug|x86 - {9A01EF03-8EAE-45CB-8B87-4A17BD904557}.Release|Any CPU.ActiveCfg = Release|x86 - {9A01EF03-8EAE-45CB-8B87-4A17BD904557}.Release|Any CPU.Build.0 = Release|x86 - {9A01EF03-8EAE-45CB-8B87-4A17BD904557}.Release-Client|Any CPU.ActiveCfg = Release|x86 - {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971}.Debug-Client|Any CPU.ActiveCfg = Debug|Any CPU - {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971}.Release|Any CPU.Build.0 = Release|Any CPU - {FB693FFB-17E3-4E84-8CBF-6FFA9C8FD971}.Release-Client|Any CPU.ActiveCfg = Release|Any CPU - {3F81E398-B223-4006-B40C-C2800714CE29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F81E398-B223-4006-B40C-C2800714CE29}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F81E398-B223-4006-B40C-C2800714CE29}.Debug-Client|Any CPU.ActiveCfg = Debug|Any CPU - {3F81E398-B223-4006-B40C-C2800714CE29}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F81E398-B223-4006-B40C-C2800714CE29}.Release|Any CPU.Build.0 = Release|Any CPU - {3F81E398-B223-4006-B40C-C2800714CE29}.Release-Client|Any CPU.ActiveCfg = Release|Any CPU - {509433F6-AEFB-44CA-BFE3-C782166D2CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {509433F6-AEFB-44CA-BFE3-C782166D2CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {509433F6-AEFB-44CA-BFE3-C782166D2CC3}.Debug-Client|Any CPU.ActiveCfg = Debug|Any CPU - {509433F6-AEFB-44CA-BFE3-C782166D2CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {509433F6-AEFB-44CA-BFE3-C782166D2CC3}.Release|Any CPU.Build.0 = Release|Any CPU - {509433F6-AEFB-44CA-BFE3-C782166D2CC3}.Release-Client|Any CPU.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {3BB3DA66-970C-4AAE-A77C-9E40F5E7E56A} = {0DDF886A-7ABB-4DCA-96A3-349B2DA00016} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1B511D10-AB55-43E0-B363-7F8838E9E977} - EndGlobalSection -EndGlobal diff --git a/legacy/V3/Tools/BuildDox.ps1 b/legacy/V3/Tools/BuildDox.ps1 deleted file mode 100644 index 3b7d97b684..0000000000 --- a/legacy/V3/Tools/BuildDox.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -$bf = $Env:APPVEYOR_BUILD_FOLDER - -$doxdir = "C:\tgsdox" - -New-Item -Path $doxdir -ItemType directory - -$publish_dox = (-not (Test-Path Env:APPVEYOR_PULL_REQUEST_NUMBER)) -and ("$Env:APPVEYOR_REPO_BRANCH" -eq "master") - -if($publish_dox){ - $github_url = "github.com/$Env:APPVEYOR_REPO_NAME" - echo "Cloning https://git@$github_url..." - git clone -b gh-pages --single-branch "https://git@$github_url" "$doxdir" 2>$null - rm -r "$doxdir\*" - Add-Content "$bf\Tools\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/tgs.ico" -}else{ - Add-Content "$bf\Tools\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/tgs.ico" -} - -doxygen.exe "$bf\Tools\Doxyfile" - -if($publish_dox){ - cd $doxdir - git config --global push.default simple - git config user.name "tgstation-server" - git config user.email "tgstation-server@tgstation13.org" - echo '# THIS BRANCH IS AUTO GENERATED BY APPVEYOR CI. SEE Tools/TGS3Build.ps1 ON MASTER' > README.md - - # Need to create a .nojekyll file to allow filenames starting with an underscore - # to be seen on the gh-pages site. Therefore creating an empty .nojekyll file. - echo "" > .nojekyll - git add --all - 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:repo_token@$github_url" 2>&1 | out-null - $Env:repo_token = "" - cd "$bf" -} diff --git a/legacy/V3/Tools/Config.dm b/legacy/V3/Tools/Config.dm deleted file mode 100644 index 2a65e3b036..0000000000 --- a/legacy/V3/Tools/Config.dm +++ /dev/null @@ -1,8 +0,0 @@ -#define SERVER_TOOLS_EXTERNAL_CONFIGURATION -#define SERVER_TOOLS_DEFINE_AND_SET_GLOBAL(Name, Value) var/##Name = ##Value -#define SERVER_TOOLS_READ_GLOBAL(Name) global.##Name -#define SERVER_TOOLS_WRITE_GLOBAL(Name, Value) global.##Name = ##Value -#define SERVER_TOOLS_WORLD_ANNOUNCE(message) world << ##message -#define SERVER_TOOLS_LOG(message) world.log << ##message -#define SERVER_TOOLS_NOTIFY_ADMINS(event) message_admins(event) -#define SERVER_TOOLS_CLIENT_COUNT clients.len \ No newline at end of file diff --git a/legacy/V3/Tools/CoverageExclusions.runsettings b/legacy/V3/Tools/CoverageExclusions.runsettings deleted file mode 100644 index 9cf93cc0e8..0000000000 --- a/legacy/V3/Tools/CoverageExclusions.runsettings +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - .*\\TGS.Tests\\.* - - - - - True - True - True - False - - - - - - - \ No newline at end of file diff --git a/legacy/V3/Tools/DMAPITravisTester.dme b/legacy/V3/Tools/DMAPITravisTester.dme deleted file mode 100644 index dd468a6dfe..0000000000 --- a/legacy/V3/Tools/DMAPITravisTester.dme +++ /dev/null @@ -1,19 +0,0 @@ -// Hand crafted DME, will not work if saved with DreamMaker - -// BEGIN_INTERNALS -// END_INTERNALS - -// BEGIN_FILE_DIR -#define FILE_DIR . -// END_FILE_DIR - -// BEGIN_PREFERENCES -// END_PREFERENCES - -// BEGIN_INCLUDE -#include "Config.dm" -#include "..\DMAPI\server_tools.dm" -#include "..\DMAPI\st_commands.dm" -#include "..\DMAPI\st_interface.dm" -#include "Test.dm" -// END_INCLUDE diff --git a/legacy/V3/Tools/Doxyfile b/legacy/V3/Tools/Doxyfile deleted file mode 100644 index 1f90072117..0000000000 --- a/legacy/V3/Tools/Doxyfile +++ /dev/null @@ -1,2479 +0,0 @@ -# Doxyfile 1.8.13 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = tgstation-server - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -# Generated in appveyor - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = "The /tg/station 13 server suite" - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -# Generated in appveyor - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -# Generated in appveyor - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = YES - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = YES - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = YES - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = NO - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = NO - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. -# The default value is: NO. - -WARN_NO_PARAMDOC = YES - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -# Generated by appveyor - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. - -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f \ - *.for \ - *.tcl \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -#We want the main .md -EXCLUDE = legacy/README.md packages - -# 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 -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = * - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = README.md - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = YES - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = YES - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = YES - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse-libclang=ON option for CMake. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = . - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /4oqIgZNM(R@ZoSql+hCGtsx_9Y3Yjwcfje|Bn9WrcZe7d4(Rr9GP&%idf z6Sm^h(brj_A9iwQ+w&VL!OpY{xR(4TUF;|ILvIWAmwb0QpNn~Bd9A1Qy5=*iC(FP* z=du@`Szl@8qMMVd!=3Y|Ifb8mSONN8f4rAJlEx5S4W(@bV+6s2J|ndGQWD%SVLezDbgcfLeezKS(qIxO->&MN z1AS#oMd+~&(MPVrm}0lW0DW>^c=k!L0ptFA@0<(T=1Q9i=N&meLfatcYY!g$6 z$I7%Ta&8#77Wah@UPFfv>mFPoUV46mz_?Lxj)Zk!nO$4dSOq<$;r#ru6@g!=OUJ4U zN^)rV!npTn%x5%%Y+D80jJDL4_F0mQLOGlpqr-L8A+g+?0tAO+Smd!r_ zy5^IhF+64}k`s#mihgB#q|QkCy#o18+VwF~NUvkQ>Im_L^wK^@yCm&`x;=$ulcxUd zFMbkOJYm)1;_n6e5GQZkp!1>M?xHTM=+)2U7j19_o7Y8br}5~gZv^#o=0P6Q2E+R6 z2gfH$7w3CTpSGtSy;n4by*R3D&_voW?l^yj$2g@2WF%x3eL;5V&+(dcGjG^drJvi5 zci)K2OUI)>WE%<1F>s#rne`8eH(%Z&-q`kB_;_ACyZ&JTeOl`OYyRUe>&Pr=_BwR4$SGuHjYO>ZT@vmDktsDlu7i* z@Cr{nv=Z%u`4o(aD98A&(+|*%1JZ_fp*CrChoRk?Iq2F};( z^z(WxWP%?2(yu=g@7jz0#~)l?j1JO98cFP`#UYxJuS1^!%;`6W9{8pEouS;Izm*Z0XG1qDjvJ=sJek|>Z<~I~8}<`?e@Xwx@4qYU ztn`s_^OXNdE9C4l=$1!aacP~&=vRJr>+h61^u<&DOR3~P{lG{Uc}@C1+WD?5jy^lI za~Ac*N?$=RedgP58oT~xkY_7>xTCn@9m2^ z*^%oY^i!v%J(l)j(xMKQMSMU%hCkZ}QM9u_(m-0GF(T(fcH^1$tC!5F0>8t}dfj-< zzK^=RuE(niq>la%qv&VfM;fB~VbDhUNHfMBRsIAYY0Nha#2lUUEl~9!-Z*!M@s}by zat=a&Ec|eefV@@sp@3DzR|-d~nY8Wc~}nY4}_-1%72zV5;0&dXai9f|hXs$-1Tw3&Yd zS?mr$=y&I#|Bodfj;VB{-z%2-=o))^5GiHU+mkr*G+yP z`B^LzEkf6y_5O=xlLlROl19=B{h=Q0sZP6kr*Lkj9c*9HS3zNnGL7>@>;v7wHvirF zr{X{TcR5$*?te)uX-41SKqsaCIa!TU75zL@&!C^{=Y^j{RUy^%b3N6+zbKUc=4UVK+{kyeZYA_h{7 zZCe3HJJfymnV6$5{hsjn&oBK!#*eT&iD$4@f_5zT_*c<|G&`VMwj%R)y!)nX1Nfew zj%PLjPrtf#3DiyUWcw>yu|Do?x&Do7>_T?X*!Ca#_L+{~p_56Q1ob#&R|(OLxku1O z8cC~8`?f6`oYu8Zfqv1%AM>DY z=G%UP@gC>3b$LjdpexVC+`w@yv3`p*y93tdIm;K_bOh%Arh-0cgOl_(J_Bv~kYL-A z9v-SKZK&{1J2>o&0_&gT8dK`-Tw};FW~@LTJ=%Ih_8qrBZ|FbU zroFV^PRDwqko{I+hcu8D(zJZxtVHC?`zX&De0k&ZF^4GEP!R*r_at9v^QOIwevn&X zBcP0-ZzsoA?!a}ezy5jvW6-a#PULC1w&%eW3#HAR`4rjDmlw02D}0g$(n6a22Kq_Q zpE0g7^o;c?ANGO7N~oQy$h8)oo17`v@Y1IQeYwDVgB-Kb{={{%^dIFM1#QZIM;ynE zpMUm=^g)GByS(R~zaU;!nLJ}|e*LXCj;}oFs*pdapE|s-d7T6Q8VB9VRxB_K4dF&qdj`r$z0?8 z+QU52XU*$(S=}q4+cW|u|A_giBQC0bxKC;mC&OB0+U&}$Tr{gP_%jT=O1ch^FMBYa z8iu~EGV^8`sSZ6_p6gtVy#R79JQ3@lk8;fyaC{w->y&*w*fw66!W!z`nC~4ntVhek z{QAY%svFmljvdtTFubcnp^xtl%^Ca#x^0zTA@lx?_~#+>Zh`OJE$}&bo<0NU1AgT` z39JjQcSA=Qn%gJmux{8Jp(FzR1oOrYF5jWiX%ir8cLT#O!H;h$`ieU6b3KxH-~m1E zNa(|Lr~`n{FJWB0yM6uY6DE!9QGP(j=shA#>mkJ6YA~S#*L$kUGo6X}zYxwv`ndp( zOZ=Y?eJ>sXe zpj@%_WWa%(FUX$F`7nM+7r`UTJ9FM;c@~^!TpJ(q%@5hEIaZ<1bC##Ud79;2V&2hb zo@29EIyH0$u@U}TSzadQWtK-~Ji4a;WifH*#^dmwt+E)*>UCE1TK21Itm?*Dy#KU$ zV1KzR4zoB6ghTeDoU@9DF+cZUi@`n&i?TS(;?Oq^hjeR}H16EkADl7NcKenv-hm_(@$1walMRFX2Tx5yU`*?+T+ ztSi?-vF_vnc|o3#H{7#^yecd0URvs*{C$Gr1M~cH4OQ3XXUlyj`gf?8HhGtbX6_kB z{gAYAofm0M^h~__oZES4KuauS6|$m#a_UE*Zp2_ z51LHHK}pfgv7noe+5f@xK>F;IR6Ay3xp&ODA&WgrvQNFUv=L?^7D|FFTW&hj3;4plq(5RNVIp0$-gw>6jKLm#w0h=TAbkPo55Q}-Z>oK_6o!&LW1rS61~tE~ zxA7gMWOqc)(#U&1ZB$m%Y{mYJ_y&v-9v;BA(UJ{*G01dl?_V0Y zIQqAyBfRtG%lKZzEdu+P81a(4XB!}t>HgJSKl<(}o!zuAw9%gUeqP*mqD;93--^i8 zK-#${gJ0gVE!ZXqWg3Un(b;GC<%?H39TW6D_j~By=cSJ3l{e$UHd?(HE7$Y^;F``g z*k{i#&0gvFU7IH#T7_?&JRvx4ix*67`D~lAxK@;FZMa8etTs1^*ZFPoTzG#L{c-sX zV&anPDTs|1pIaYwKbb%L_7V1<4y13I)Qf5PjVP}F@CwUroo>u`w$%!J3mGA!|^;5t4rJF_@|3?pKSF9?JgujDfnm^kvya#MX;gD^u z?+;~K?|A8T=Eh6szLyUC@w*%Da!cYrbwBpgdRRRB`oOf&A0!jjEtAG%>;Zn-O6RGc z^eRoN@y9+;l~r2K?m8#?e$cMT@0%l(8hJ?liQnc+7tc#0{y1hySN%=P;dn<6w&Vl9 z;}Etll?WEO7oMrT$&`0q>2+Ru-H#RLmIZn~$y`(4i&bW6i05}f^?>igSf!VC^>puf zxEbHPC@J3RyfmR9Rrs?tt~aTRY1#?Hy6J37N!x9HVhzhki!3b3HMu$?P1+$9_zOC& zG@;v8TAgqD+@P)e)4A68t_3qv@(%goh0aGm=~bFk;E&&aQTdnl^P-!ti?ELNSljfb zb3dhd`D3lq@0TADCgQ^HGfbBX{AJQQm9W2yWZz%6ziQn_hnPS}OaIGn9GSGe&9st% z;UANHZR3wVE+*xdhO|2GZ2O3DT!WAn{F&^-da-o z6mHwHDZ;*>{vWY;PE4c=f81Ls79FXK=lT#mn3QE3f49w;;F||-y7f8)xqji%3RbCv(p2-P(8ir3!zfpZ&02&v@5i z9|BqJrStQBKNl}g{T?z-i~Q5ew0TXPB#}?0WM25kq-@*x8_~0+@AgY2{;nJ0Uk)>^ z3iR(Y;h&$h$UEck$N6yj!v_g|c%ssjRs7vJX_#*u>Gy>h@nVdtsmdl=i>LtRryO<>o0@B?CVrzGd;}jlWsf zk1*n+yAAdKi|0&Xd!`hBCh}gV&Feb6Cv0F0dgU1_E{i{J{4JR~)f)bY#D&KsL3DH9E#(U6aOSL#({L;)w{H1Dd4bFQf8&qBWoU{0g zjlY()es5jAuuZt%a55OAeb`Ui21(PcMU7c?-=-t=<~g27h`o=%Be zR`eww5; zwF{5`VJBt zFPM~FR`F-@-7>HFH=V!lz5SX<<(?u7Z=S^BVq);6EgMsYJz|BvgEm7Je?s~+`{hF- z{IPAg=f2T7qb58s!5`be1luNbFfI4bL2zvP-+0lGtdA*)j1|1f zixpQE@yGp1V%66xp8MvR1d~0!yvj=y$8XQ2()MlozM1fbX%p2?zhBnycgxgKM)fBz z!rziHVl&%N=cCPQV(*SQH>4zg?pc-UV8V;CgukHse+K=&SgK@Hc>RxK@~uR_ZJuiE zF@L|bFkPnOkKY{N`W}9N-K5?sEyokd1eK4ibE7klBsp>n!tWc~%2(yX?>(kEsIvT= zOA>$Ng&!JIm4?2X`u9Ig+a(oagejx@kPL}G&Pmv=dw4%q)2bS!H=+4Z#UJJ6vl|{Y z;;TC`ziDW~Yd>lE&1{5-4Q5cMhStk)*TeRIKlM~;4mJGIe`4O#q| zwSdiiMpZk7<#Sy8{S7{dD!r|yPExy?wbdEVzd(f_j;2V2P zn~MF0!utdJ=0l={vFhs;Uy}GsR1ZJ*>1)bw&e5joRlh`W9LxF%saXFJbUz~7{;ynP zYBMC_xqd&>@u%}`!mwa_(E8bL(e^rEKQ_!Q~0-Z<4@-rm}`V^2ziY(tiB!*WSO> zs>}hIfw9MDP1-n7THEW85|yXlFN^r2PpZk@X@1s2*!IOs27lalQg6d#uE{6c!GxZ& zh(GeP$vH>+ZX-YVW1c2_O_cVn*Ir!Fnu3yuK)^^bIPjp>Y@n^C=%`g6J?m0^TL4-i~Gx^4HqV~Ua=6Kui zGm_0^6@Mno;E6%B+nr_|WRb*6_!DHB%A1>KZF9i$62`+5F}Pv1E$B*;7VZ z!(Xb_0>{FdS9}(KX85zY<~QtnXC(iIb-%qL4Cr{VF$PV_$l@;=e=BaEnf7)jw)|i)6?+i*!3x(YhxcXmg+)Jk zsx(>rl`Q`J>^-DoGo8AD!U)eM-|EtzOLT1;fAp(NRHnKg-gD8{J7tvC?&Kb$&`Q z81!2;C5gY%BL9N=-=|vs5i_yy6eNE9kS;01U{KjqlK3kv@-OK6qg3LLSP6o$M9(al zJ5^%Prf)1UL|&-Cy;7fD|A_h<_gs?rD=qSmeTQE@Odmfu!p~If^-Vg65rm}r+j!3t z8$tJAip3`#fA060zB}9xlX@X}#=ZNwMxP;8eqQlGWvnd7<0tou*1pS6mHubyC!+I! z-y-zlQ_#4k=TsFN*WO1?r3`y|-klnsVU7NH_zTJp_j5Y$J>X;y?w)t1=VuXM3MAulBVar?AH}zkeDue?DvC&u0^BvGU@)SKK$Nw-g4n7v6@T<$^wajGA&tKGY0$mBZQGP>;!foiWZinE z;?MT}#Hqxo6nxmA*S2#0DPA2*;-0(C+y^5l z9jI$Y$Ua3U{ptPIb>YpER8z_`?fZOhx&JTiwwz;+g~wR&?y}u&5mlgtKkvN$Vqp7| zo7QCi(>~{3*QI<%g5(`u80(}h3P z_A3|967-R=?JMNPQ`(ke@h_;YPcJ7Eu}7MFUs^j#HtBVWbxl30Puq(G{jj)S%({nG zM*OW*+h*dKa{I}LS4+KuePiI`uKAKDTQ@zPkS{j&v1c2yEi>`BYrza-{Kdj0fZw)2RWefcDZ07Wkl(tdDjj{OhjeW!xW5y9 z@mNmyyF7uALf@CMgF8vxj`|(185cBA$DmC2d+-~n^tUSM|Y4+nv^;!<_JT2Hxw+$i|-n`;DPW1}3L2}P9 zw|&Pek63a1eh+0@cwI!XVoc|WVwIDs_;leT)#cb+E0PLctN9oD(Q>Zd0v)f z`Kj!})YtqzTN3HQ`%t7sKBXFev`v)+53^-f4$SxPHw(%H{xZc+DT~)4yoSFOUMBEI z`D*ifI$4|);UpEj;P-!1-6y6y|2fmf1nvt`!Q*V6u`x{hb-MA9uJSmpz#z z+HWt}HY)4Smj++MRAV~4S9_V67b|O+O!aphQ;n~9`IiNM(6WX%ulfX8_Y|+)OT)cP z*n7&WEe_Q4``QkziPYP zR6jkcPn$y#d#tk%2lM19>Su4YsK4@0VWr}61?7v!IW*BnhXan&EILMry^d4W9arc$ z#R|!HoN`g=3Mw6R5b_GOvI`i)pvC3O2>fV|t0jXJ2%EM6Syr9w58 z&MR(@N~Q6kuqZCV2d}t-qI8i8=NH9AD(n?klq|vot-9cAh-i&u#|c-YP=7W|pjL$9 zbX0cX45P9OXBd@TxaIW{cnxAS#)s}Uli-O?2?||B$U@3WoCcOqz47Xl_PE067LpBI z;En@)^WAZPuUDKq8NlH}8dKyIg9~JjS6ne@05HW^ig&ZHc)69l;tGnVE1zFHu3Ygb z|1n^TLZ)xn4Z*;=QNbC~&tgWKN+w4!v11VjyFS6^y>8SiyOsIEWDCCPwzk zE@EWw?J8JmMP6|(&QxO=e&bvfX~t*$#Sk8#xO}~p!b(jtaZ$_< zD^6Z7lz`r zA|e1*1j~eBd{hfP75WWR-As=Qw`ZOnhqkMPa-yO@VBSGpqvl Date: Wed, 25 Jul 2018 11:05:56 -0400 Subject: [PATCH 322/397] Appveyor buff --- appveyor.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b72de09803..e970d12c50 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,11 +16,11 @@ configuration: shallow_clone: true artifacts: - path: artifacts/ServerConsole - name: Server Console + name: ServerConsole - path: artifacts/ServerService - name: Server Service + name: ServerService - path: artifacts/ServerHost - name: Server Auto Update + name: ServerUpdatePackage cache: - ~\.nuget\packages -> **\*.csproj - C:\ProgramData\chocolatey\bin -> appveyor.yml @@ -60,17 +60,22 @@ after_test: - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION% - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse + - ps: Remove-Item artifacts/ServerHost/appsettings.Docker.json - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerService/lib/Default -recurse + - ps: Move-Item -path artifacts/ServerService/lib/Default/appsettings.json -destination artifacts/ServerService/ - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerConsole/lib/Default -recurse + - ps: Move-Item -path artifacts/ServerConsole/lib/Default/appsettings.json -destination artifacts/ServerConsole/ - 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." }}} deploy: - provider: GitHub release: "tgstation-server-v$(TGSVersion)" description: 'The /tg/station server suite' auth_token: secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK - artifact: TGS3Server,MD5SHA1Server,TGS3Client,MD5SHA1Client,TGS3ServerConsole,MD5SHA1ServerConsole - draft: false + artifact: ServerConsole,ServerService,ServerUpdatePackage + draft: true on: TGSDeploy: "Do it." - provider: NuGet From 7226ccc229019b84de268fc71bd517eae24bc1a2 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 12:05:54 -0400 Subject: [PATCH 323/397] Kill off another host watchdog test --- .../TestWatchdog.cs | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs index 15685e68e4..7f3c05c8a2 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs @@ -22,31 +22,5 @@ namespace Tgstation.Server.Host.Watchdog.Tests var mockLogger = new LoggerFactory().CreateLogger(); var wd = new Watchdog(mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); } - - class MockServerFactory : IServerFactory - { - readonly IServer server; - public MockServerFactory(IServer server) => this.server = server; - public IServer CreateServer(string[] args, string updatePath) => server; - } - - [TestMethod] - public async Task TestRunAsyncWithoutUpdate() - { - var mockServer = new Mock(); - var mockServerFactory = new MockServerFactory(mockServer.Object); - var mockActiveAssemblyDeleter = new Mock(); - var mockIsolatedServerContextFactory = new Mock(); - var mockLogger = new LoggerFactory().CreateLogger(); - - var wd = new Watchdog(mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); - - using (var cts = new CancellationTokenSource()) - { - mockServer.Setup(x => x.RunAsync(cts.Token)).Returns(Task.CompletedTask).Verifiable(); - await wd.RunAsync(Array.Empty(), cts.Token).ConfigureAwait(false); - mockServer.VerifyAll(); - } - } } } From 830471f4b7a91c67cd8cc1a5b23f793509e0a468 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 12:07:24 -0400 Subject: [PATCH 324/397] The enitre fucking administration controller --- .../Models/Administration.cs | 7 +- .../Rights/AdministrationRights.cs | 2 +- .../Configuration/UpdatesConfiguration.cs | 28 +++ .../Controllers/AdministrationController.cs | 160 ++++++++++++++++++ src/Tgstation.Server.Host/Core/Application.cs | 14 +- src/Tgstation.Server.Host/appsettings.json | 5 + 6 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs create mode 100644 src/Tgstation.Server.Host/Controllers/AdministrationController.cs diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs index 9e51bd7d3c..99421efdbe 100644 --- a/src/Tgstation.Server.Api/Models/Administration.cs +++ b/src/Tgstation.Server.Api/Models/Administration.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models @@ -7,6 +6,12 @@ namespace Tgstation.Server.Api.Models /// public sealed class Administration { + /// + /// The GitHub repository the server is built to recieve updates from + /// + [Permissions(DenyWrite = true)] + public Uri TrackedRepositoryUrl { get; set; } + /// /// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is higher than 's the update cannot be applied due to API changes /// diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index 58f208711c..3aaad4d552 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -19,7 +19,7 @@ namespace Tgstation.Server.Api.Rights /// /// User can gracefully restart the host /// - SoftStop = 2, + RestartHost = 2, /// /// User can change /// diff --git a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs new file mode 100644 index 0000000000..3a5aff123a --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs @@ -0,0 +1,28 @@ +namespace Tgstation.Server.Host.Configuration +{ + /// + /// Configuration for the automatic update system + /// + sealed class UpdatesConfiguration + { + /// + /// The key for the the resides in + /// + public const string Section = "Updates"; + + /// + /// The of the tgstation-server fork to recieve updates from + /// + public long GitHubRepositoryId { get; set; } + + /// + /// Prefix before the of TGS published in git tags + /// + public string GitTagPrefix { get; set; } + + /// + /// Asset package containing the new assembly in zip form + /// + public string UpdatePackageAssetName { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs new file mode 100644 index 0000000000..a7819ba632 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -0,0 +1,160 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Octokit; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// for + /// + [Route("/" + nameof(Administration))] + sealed class AdministrationController : ModelController + { + /// + /// HTTP 429 status code + /// + const int RateLimitHttpStatusCode = 429; + + /// + /// The for the + /// + readonly IGitHubClient gitHubClient; + + /// + /// The for the + /// + readonly IServerUpdater serverUpdater; + + /// + /// The for the + /// + readonly IApplication application; + + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// The for the + /// + readonly UpdatesConfiguration updatesConfiguration; + + /// + /// Construct an + /// + /// The for the + /// The for the + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The containing value of + public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); + this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions)); + } + + StatusCodeResult RateLimit(RateLimitExceededException exception) + { + logger.LogWarning("Exceeded GitHub rate limit!"); + var secondsString = Math.Ceiling((exception.Reset - DateTimeOffset.Now).TotalSeconds).ToString(CultureInfo.InvariantCulture); + Response.Headers.Add("Retry-After", new Microsoft.Extensions.Primitives.StringValues { }); + return StatusCode(RateLimitHttpStatusCode); + } + + /// + [TgsAuthorize] + public override async Task Read(CancellationToken cancellationToken) + { + var model = new Administration + { + CurrentVersion = application.Version + }; + try + { + var repositoryTask = gitHubClient.Repository.Get(updatesConfiguration.GitHubRepositoryId); + var releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)); + + Version greatestVersion = null; + foreach (var I in releases) + if (Version.TryParse(I.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty), out var version) + && version.Major == application.Version.Major + && (greatestVersion == null || version > greatestVersion)) + greatestVersion = version; + + model.LatestVersion = greatestVersion; + model.TrackedRepositoryUrl = new Uri((await repositoryTask.ConfigureAwait(false)).HtmlUrl); + } + catch (RateLimitExceededException e) + { + return RateLimit(e); + } + return Json(model); + } + + /// + [TgsAuthorize(AdministrationRights.ChangeVersion)] + public override async Task Update([FromBody] Administration model, CancellationToken cancellationToken) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + + if (model.CurrentVersion == null) + return BadRequest(new { message = "Missing new version!" }); + + if (model.CurrentVersion.Major != application.Version.Major) + return BadRequest(new { message = "Cannot update to a different suite version!" }); + + IEnumerable releases; + try + { + releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)); + } + catch (RateLimitExceededException e) + { + return RateLimit(e); + } + + foreach (var release in releases) + if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty), out var version) && version == model.CurrentVersion) + { + var asset = release.Assets.Where(x => x.Name == updatesConfiguration.UpdatePackageAssetName).FirstOrDefault(); + if (asset == default) + continue; + + var assetBytes = await ioManager.DownloadFile(new Uri(asset.Url), cancellationToken).ConfigureAwait(false); + await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false); + return Ok(); //gtfo of here before all the cancellation tokens fire + } + + return StatusCode((int)HttpStatusCode.Gone); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 689ee11fba..48b5affc83 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; +using Octokit; using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; @@ -29,6 +30,11 @@ namespace Tgstation.Server.Host.Core /// sealed class Application : IApplication { + /// + /// Prefix for string version names + /// + const string VersionPrefix = "/tg/station server"; + /// public string HostingPath => serverAddresses.Addresses.First(); @@ -64,7 +70,7 @@ namespace Tgstation.Server.Host.Core this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); Version = Assembly.GetExecutingAssembly().GetName().Version; - VersionString = String.Format(CultureInfo.InvariantCulture, "/tg/station server v{0}", Version); + VersionString = String.Format(CultureInfo.InvariantCulture, "{0} v{1}", VersionPrefix, Version); } /// @@ -77,7 +83,8 @@ namespace Tgstation.Server.Host.Core { if (services == null) throw new ArgumentNullException(nameof(services)); - var workingDir = Environment.CurrentDirectory; + + services.Configure(configuration.GetSection(UpdatesConfiguration.Section)); var databaseConfigurationSection = configuration.GetSection(DatabaseConfiguration.Section); services.Configure(databaseConfigurationSection); @@ -146,9 +153,10 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); - services.AddSingleton, PasswordHasher>(); + services.AddSingleton, PasswordHasher>(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(x => new GitHubClient(new ProductHeaderValue(VersionPrefix, Version.ToString()))); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 8abbd1eb6a..3ea279ea3c 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -18,6 +18,11 @@ } } }, + "Updates": { + "GitHubRepositoryId": 92952846, + "GitTagPrefix": "tgstation-server-v", + "UpdatePackageAssetName": "ServerUpdatePackage.zip" + }, "Database": { "DatabaseType": "SqlServer", "ResetAdminPassword": false, From fb8b1478b9d421e0458f9d2b196316b5cfc4cde8 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 12:20:44 -0400 Subject: [PATCH 325/397] Docker fixups --- .dockerignore | 3 --- build/Dockerfile | 8 ++++++-- tgstation-server.sln | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.dockerignore b/.dockerignore index 341bd88cf1..24e31346b4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,11 +1,8 @@ .dockerignore -docker-compose.yml .git .gitignore .vs .vscode -legacy packages -build */bin */obj diff --git a/build/Dockerfile b/build/Dockerfile index e4d8f487e7..277e79bb8e 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/dotnet:2.0-sdk AS build +FROM microsoft/dotnet:2.1-sdk AS build WORKDIR /src COPY tgstation-server.sln ./ @@ -33,9 +33,13 @@ RUN dotnet test -c Release WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests RUN dotnet test -c Release +WORKDIR /src/src/Tgstation.Server.Host.Console RUN dotnet publish -c Release -o /app -FROM microsoft/dotnet:2.0-runtime +WORKDIR /src/src/Tgstation.Server.Host +RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app + +FROM microsoft/dotnet:2.1-aspnetcore-runtime EXPOSE 80 WORKDIR /app diff --git a/tgstation-server.sln b/tgstation-server.sln index 360c9998b3..9541fb3e74 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DA32568D-1D8D-4A4C-9943-BFD3CE796B3F}" ProjectSection(SolutionItems) = preProject .codecov.yml = .codecov.yml + .dockerignore = .dockerignore .gitignore = .gitignore .travis.yml = .travis.yml appveyor.yml = appveyor.yml @@ -102,7 +103,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v4", "v4", "{057FAC33-CC31- src\DMAPI\tgs\v4\commands.dm = src\DMAPI\tgs\v4\commands.dm EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Console.Debug", "src\Tgstation.Server.Host.Console.Debug\Tgstation.Server.Host.Console.Debug.csproj", "{15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Console.Debug", "src\Tgstation.Server.Host.Console.Debug\Tgstation.Server.Host.Console.Debug.csproj", "{15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 6640a6c8edc1107d75f4e53c5a93cf3c21a4f763 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 12:26:50 -0400 Subject: [PATCH 326/397] NANI!? --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index e970d12c50..566210beea 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -62,6 +62,7 @@ after_test: - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse - ps: Remove-Item artifacts/ServerHost/appsettings.Docker.json - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerService/lib/Default -recurse + - ps: ls artifacts/ServerService - ps: Move-Item -path artifacts/ServerService/lib/Default/appsettings.json -destination artifacts/ServerService/ - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerConsole/lib/Default -recurse - ps: Move-Item -path artifacts/ServerConsole/lib/Default/appsettings.json -destination artifacts/ServerConsole/ From 4f1874fe1e927f0376df31396b1776864b8c8ee1 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 13:31:57 -0400 Subject: [PATCH 327/397] Full successful test of the administration controller --- .../Routes/Administration.cs | 13 + .../Configuration/UpdatesConfiguration.cs | 2 +- .../Controllers/AdministrationController.cs | 14 +- .../Controllers/ModelController.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 2 +- .../Core/IServerUpdater.cs | 7 +- .../IO/DefaultIOManager.cs | 2 +- src/Tgstation.Server.Host/Server.cs | 8 +- .../Tgstation.Server.Host.csproj | 1 + tgstation-server.sln | 5 + tools/TGS.postman_collection.json | 251 ++++++++++++++++++ 11 files changed, 296 insertions(+), 11 deletions(-) create mode 100644 src/Tgstation.Server.Api/Routes/Administration.cs create mode 100644 tools/TGS.postman_collection.json diff --git a/src/Tgstation.Server.Api/Routes/Administration.cs b/src/Tgstation.Server.Api/Routes/Administration.cs new file mode 100644 index 0000000000..e5d7b1cfcb --- /dev/null +++ b/src/Tgstation.Server.Api/Routes/Administration.cs @@ -0,0 +1,13 @@ +namespace Tgstation.Server.Api.Routes +{ + /// + /// Routes for + /// + public static class Administration + { + /// + /// The base route + /// + public const string Base = "/" + nameof(Administration); + } +} diff --git a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs index 3a5aff123a..4d03a251cb 100644 --- a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs @@ -3,7 +3,7 @@ /// /// Configuration for the automatic update system /// - sealed class UpdatesConfiguration + public sealed class UpdatesConfiguration { /// /// The key for the the resides in diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index a7819ba632..dceca3f0f8 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -22,8 +22,8 @@ namespace Tgstation.Server.Host.Controllers /// /// for /// - [Route("/" + nameof(Administration))] - sealed class AdministrationController : ModelController + [Route(Api.Routes.Administration.Base)] + public sealed class AdministrationController : ModelController { /// /// HTTP 429 status code @@ -149,12 +149,20 @@ namespace Tgstation.Server.Host.Controllers if (asset == default) continue; - var assetBytes = await ioManager.DownloadFile(new Uri(asset.Url), cancellationToken).ConfigureAwait(false); + var assetBytes = await ioManager.DownloadFile(new Uri(asset.BrowserDownloadUrl), cancellationToken).ConfigureAwait(false); await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false); return Ok(); //gtfo of here before all the cancellation tokens fire } return StatusCode((int)HttpStatusCode.Gone); } + + /// + [TgsAuthorize(AdministrationRights.RestartHost)] + public override Task Delete(long id, CancellationToken cancellationToken) + { + serverUpdater.Restart(); + return Task.FromResult((IActionResult)Ok()); + } } } diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 54ea395d7e..7943e4ab91 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -49,7 +49,7 @@ namespace Tgstation.Server.Host.Controllers /// The ID of the model to get /// The for the operation /// A resulting in the of the operation - [HttpGet("/{0}")] + [HttpGet("/{id}")] public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 48b5affc83..839951c716 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Core /// /// Prefix for string version names /// - const string VersionPrefix = "/tg/station server"; + const string VersionPrefix = "tgstation-server"; /// public string HostingPath => serverAddresses.Addresses.First(); diff --git a/src/Tgstation.Server.Host/Core/IServerUpdater.cs b/src/Tgstation.Server.Host/Core/IServerUpdater.cs index ed5a4e76f0..270ffc1636 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdater.cs +++ b/src/Tgstation.Server.Host/Core/IServerUpdater.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Core /// /// Represents a service that may take an updated assembly and run it, stopping the current assembly in the process /// - interface IServerUpdater + public interface IServerUpdater { /// /// Run a new assembly and stop the current one. This will likely trigger all active s @@ -24,5 +24,10 @@ namespace Tgstation.Server.Host.Core /// /// The to run void RegisterForUpdate(Action action); + + /// + /// Restarts the + /// + void Restart(); } } diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index a5f2b122c7..3b2addc55f 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -274,7 +274,7 @@ namespace Tgstation.Server.Host.IO { string GetEntryName(ZipArchiveEntry entry) => ConcatPath(path, entry.FullName); //create directories first - await Task.WhenAll(archive.Entries.Where(x => x.Name.Length == 0).Select(x => CreateDirectory(GetEntryName(x), cancellationToken))).ConfigureAwait(false); + await Task.WhenAll(CreateDirectory(path, cancellationToken), Task.WhenAll(archive.Entries.Where(x => x.Name.Length == 0).Select(x => CreateDirectory(GetEntryName(x), cancellationToken)))).ConfigureAwait(false); //extract files await Task.WhenAll(archive.Entries.Where(x => x.Name.Length > 0).Select(async x => { diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index e01c600962..ab2cb8874e 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -56,14 +56,13 @@ namespace Tgstation.Server.Host [ExcludeFromCodeCoverage] public async Task RunAsync(CancellationToken cancellationToken) { - Console.WriteLine("Hello world!"); using (cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) using (var webHost = webHostBuilder .UseStartup() .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) - await webHost.RunAsync(cancellationToken).ConfigureAwait(false); + await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false); } /// @@ -83,11 +82,14 @@ namespace Tgstation.Server.Host UpdateGuid = null; throw; } - cancellationTokenSource.Cancel(); + Restart(); } } /// public void RegisterForUpdate(Action action) => cancellationTokenSource.Token.Register(action); + + /// + public void Restart() => cancellationTokenSource.Cancel(); } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 5b23cc7789..53b174f006 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -3,6 +3,7 @@ netstandard2.0 Full + 4.0.0.0 diff --git a/tgstation-server.sln b/tgstation-server.sln index 9541fb3e74..3a752ae3e9 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -105,6 +105,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v4", "v4", "{057FAC33-CC31- EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Console.Debug", "src\Tgstation.Server.Host.Console.Debug\Tgstation.Server.Host.Console.Debug.csproj", "{15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{A55C1117-5808-4AB2-BEA6-4D4A3E66A2F2}" + ProjectSection(SolutionItems) = preProject + tools\TGS.postman_collection.json = tools\TGS.postman_collection.json + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json new file mode 100644 index 0000000000..59bebb834f --- /dev/null +++ b/tools/TGS.postman_collection.json @@ -0,0 +1,251 @@ +{ + "info": { + "_postman_id": "f0940866-29fd-4ea3-8682-7e30df5b08c9", + "name": "TGS", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Administration", + "description": "", + "item": [ + { + "name": "Get Update Versions", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + } + ], + "body": {}, + "url": { + "raw": "localhost:5000/Administration", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Administration" + ] + } + }, + "response": [] + }, + { + "name": "Mock Update", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"currentVersion\": {\n \"major\": 3,\n \"minor\": 2,\n \"build\": 3,\n \"revision\": 6,\n \"majorRevision\": 0,\n \"minorRevision\": 0\n }\n}" + }, + "url": { + "raw": "localhost:5000/Administration", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Administration" + ] + } + }, + "response": [] + }, + { + "name": "Restart Server", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + } + ], + "body": {}, + "url": { + "raw": "localhost:5000/Administration", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Administration" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "1f443013-474b-4f66-afc1-3968552a4a7b", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "e2f3d034-2618-428f-a956-7d83fcd5e309", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "Login Admin Default", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Authorization", + "value": "Password ISolemlySwearToDeleteTheDataDirectory" + }, + { + "key": "Username", + "value": "admin" + } + ], + "body": {}, + "url": { + "raw": "localhost:5000", + "host": [ + "localhost" + ], + "port": "5000" + } + }, + "response": [] + }, + { + "name": "VersionGet", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "name", + "value": "asdfasdf", + "description": "", + "type": "text" + } + ] + }, + "url": { + "raw": "localhost:5000", + "host": [ + "localhost" + ], + "port": "5000" + } + }, + "response": [] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjU0MjgwMyIsIm5iZiI6IjE1MzI1MzkyMDMiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.f-y5xQ-JbZklD7iMeztZhLGfXBWnSxxlYtzl3TXDt5g", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "id": "cb655975-15d0-4115-9849-4493db7593c1", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "55b4fd6c-39db-4c6b-9fe4-a9a2ebe7e09c", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file From 944970d796e34bcb076145c4cda0bda87d75efff Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 15:56:26 -0400 Subject: [PATCH 328/397] Oh great... upstream bugs... --- src/Tgstation.Server.Host.Console/Program.cs | 1 - src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs | 2 +- src/Tgstation.Server.Host.Watchdog/Watchdog.cs | 2 -- tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs | 4 +++- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index b407ddbcb1..4bdac33a6d 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Console; using System.Collections.Generic; using System.Threading.Tasks; using Tgstation.Server.Host.Watchdog; diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs index 654577329b..14bae38479 100644 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs @@ -63,7 +63,7 @@ namespace Tgstation.Server.Host.Watchdog return serverFactory.CreateServer(args, updatePath); } - //honestly have no idea what this is for, but the examples i see just return null and it seems to work just fine + //honestly have no idea what this is for, https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/assemblyloadcontext.md /// protected override Assembly Load(AssemblyName assemblyName) => null; } diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index a481118eef..6ef0b74b74 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -4,14 +4,12 @@ using System.IO; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host.Watchdog { /// sealed class Watchdog : IWatchdog { - /// /// The for the /// diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 6a8eb5d68a..da80b4917b 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -16,7 +16,9 @@ namespace Tgstation.Server.Host.Core.Tests public Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException(); public void RegisterForUpdate(Action action) => throw new NotImplementedException(); - + + public void Restart() => throw new NotImplementedException(); + [TestMethod] public async Task TestSuccessfulStartup() { From 571792ed6d8073781588b9c59cedb2aef1b0b73c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 25 Jul 2018 16:12:15 -0400 Subject: [PATCH 329/397] Mild bit of race condition prevention --- .../Controllers/AdministrationController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index dceca3f0f8..cd966e7b6d 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -150,7 +150,11 @@ namespace Tgstation.Server.Host.Controllers continue; var assetBytes = await ioManager.DownloadFile(new Uri(asset.BrowserDownloadUrl), cancellationToken).ConfigureAwait(false); - await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false); + try + { + await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false); + } + catch (InvalidOperationException) { } //we were beat to the punch return Ok(); //gtfo of here before all the cancellation tokens fire } From 5490c1e1efa4db627fa88a6c723bb1266cb9768a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 26 Jul 2018 17:35:42 -0400 Subject: [PATCH 330/397] Fix windows system identities --- .../Controllers/ApiController.cs | 7 ++ .../Controllers/HomeController.cs | 28 +++++-- .../Controllers/UserController.cs | 7 +- src/Tgstation.Server.Host/Core/Application.cs | 3 +- .../Security/AuthenticationContextFactory.cs | 77 ++++++++++++++++--- .../Security/IAuthenticationContextFactory.cs | 12 ++- .../Security/ITokenFactory.cs | 6 +- .../Security/IdentityCache.cs | 63 +++++++++++++++ .../Security/TokenFactory.cs | 11 ++- .../Security/WindowsSystemIdentity.cs | 31 +++++++- .../Security/WindowsSystemIdentityFactory.cs | 22 +++--- src/Tgstation.Server.Host/Server.cs | 14 +++- 12 files changed, 234 insertions(+), 47 deletions(-) create mode 100644 src/Tgstation.Server.Host/Security/IdentityCache.cs diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 4c6bcd541f..30d503f871 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -115,6 +115,13 @@ namespace Tgstation.Server.Host.Controllers /// public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { + if (AuthenticationContext == null) + { + //accessing an instance they don't have access to + await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + //validate the headers try { diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index a1849bfc86..414593875e 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using System; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -33,6 +34,10 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// readonly IApplication application; + /// + /// The for the + /// + readonly IAuthenticationContextFactory authenticationContextFactory; /// /// Construct a @@ -49,6 +54,8 @@ namespace Tgstation.Server.Host.Controllers this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.application = application ?? throw new ArgumentNullException(nameof(application)); + //base checks not null + this.authenticationContextFactory = authenticationContextFactory; } /// @@ -81,6 +88,7 @@ namespace Tgstation.Server.Host.Controllers if (user == null) return Unauthorized(); + ISystemIdentity identity = null; if (user.PasswordHash != null) { var originalHash = user.PasswordHash; @@ -101,17 +109,23 @@ namespace Tgstation.Server.Host.Controllers else try { - using (await systemIdentityFactory.CreateSystemIdentity(user.Name, ApiHeaders.Password, cancellationToken).ConfigureAwait(false)) { } + identity = await systemIdentityFactory.CreateSystemIdentity(user.Name, ApiHeaders.Password, cancellationToken).ConfigureAwait(false); + if (identity == null || identity.Uid != user.SystemIdentifier) + return Unauthorized(); } - catch + catch (NotImplementedException) { - return Unauthorized(); + return StatusCode((int)HttpStatusCode.NotImplemented); } + using (identity) { + if (!user.Enabled.Value) + return Forbid(); - if (!user.Enabled.Value) - return Forbid(); - - return Json(tokenFactory.CreateToken(user)); + var token = tokenFactory.CreateToken(user, out var expiry); + if (identity != null) + authenticationContextFactory.CacheSystemIdentity(user, identity, expiry.AddSeconds(10)); //expire the identity slightly after the auth token in case of lag + return Json(token); + } } } } diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 59638e2026..376ccd3b26 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -82,6 +82,8 @@ namespace Tgstation.Server.Host.Controllers { using (var sysIdentity = await systemIdentityFactory.CreateSystemIdentity(dbUser, cancellationToken).ConfigureAwait(false)) { + if (sysIdentity == null) + return StatusCode((int)HttpStatusCode.Gone); dbUser.Name = sysIdentity.Username; dbUser.SystemIdentifier = sysIdentity.Uid; } @@ -90,11 +92,6 @@ namespace Tgstation.Server.Host.Controllers { return StatusCode((int)HttpStatusCode.NotImplemented); } - catch(Exception e) - { - logger.LogInformation("System identifier user creation failure for {0}. Exception: {1}", model.SystemIdentifier, e); - return Forbid(); - } else cryptographySuite.SetUserPassword(dbUser, model.Password); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 839951c716..c4f285701f 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -149,7 +149,8 @@ namespace Tgstation.Server.Host.Core throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}!", nameof(DatabaseType))); } - services.AddScoped(); + //very important this remains a singleton + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 1b73c7afc5..3bddfce971 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -1,14 +1,16 @@ using Microsoft.EntityFrameworkCore; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { /// - sealed class AuthenticationContextFactory : IAuthenticationContextFactory + sealed class AuthenticationContextFactory : IAuthenticationContextFactory, IDisposable { /// public IAuthenticationContext CurrentAuthenticationContext { get; private set; } @@ -19,19 +21,53 @@ namespace Tgstation.Server.Host.Security readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the + /// The for the /// - readonly IDatabaseContext databaseContext; + readonly IDatabaseContextFactory databaseContextFactory; + + /// + /// Map of s to s for that user + /// + readonly Dictionary identityCache; /// /// Construct an /// /// The value of - /// The value of - public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContext databaseContext) + /// The value of + public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContextFactory databaseContextFactory) { this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); - this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + + identityCache = new Dictionary(); + } + + /// + public void Dispose() + { + foreach (var I in identityCache) + I.Value.Dispose(); + } + + /// + public void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + if (systemIdentity == null) + throw new ArgumentNullException(nameof(systemIdentity)); + lock (identityCache) + { + if (identityCache.TryGetValue(user.Id, out var identCache)) + identCache.Dispose(); //also clears it out + identCache = new IdentityCache(systemIdentity.Clone(), expiry, () => + { + lock (identityCache) + identityCache.Remove(user.Id); + }); + identityCache.Add(user.Id, identCache); + } } /// @@ -40,18 +76,35 @@ namespace Tgstation.Server.Host.Security if (CurrentAuthenticationContext != null) throw new InvalidOperationException("Authentication context has already been loaded"); - var userQuery = databaseContext.Users.Where(x => x.Id == userId); + User user = null; + await databaseContextFactory.UseContext(async db => + { + var userQuery = db.Users.Where(x => x.Id == userId); - if (instanceId.HasValue) - userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId)); + if (instanceId.HasValue) + userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId)); - var user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false); + user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); InstanceUser instanceUser = null; if (instanceId.HasValue) - instanceUser = user.InstanceUsers.Where(x => x.InstanceId == instanceId).First(); + instanceUser = user.InstanceUsers.Where(x => x.InstanceId == instanceId).FirstOrDefault(); + + if (instanceUser == default) + return; + + ISystemIdentity systemIdentity; + if (user.SystemIdentifier != null) + lock (identityCache) + { + if (!identityCache.TryGetValue(userId, out var identCache)) + throw new InvalidOperationException("Cached system identity has expired!"); + systemIdentity = identCache.SystemIdentity.Clone(); + } + else + systemIdentity = null; - var systemIdentity = user.SystemIdentifier != null ? await systemIdentityFactory.CreateSystemIdentity(user, cancellationToken).ConfigureAwait(false) : null; CurrentAuthenticationContext = new AuthenticationContext(systemIdentity, user, instanceUser); } } diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs index e0f3a225f2..2e8552c037 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs @@ -1,5 +1,7 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { @@ -13,6 +15,14 @@ namespace Tgstation.Server.Host.Security /// IAuthenticationContext CurrentAuthenticationContext { get; } + /// + /// Keep a 's alive until an time + /// + /// The the belongs to + /// The to cache + /// When the should expire + void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry); + /// /// Create an to populate /// diff --git a/src/Tgstation.Server.Host/Security/ITokenFactory.cs b/src/Tgstation.Server.Host/Security/ITokenFactory.cs index 77f8a1518c..7f40b2112d 100644 --- a/src/Tgstation.Server.Host/Security/ITokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/ITokenFactory.cs @@ -1,5 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; +using System; using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Security @@ -13,7 +12,8 @@ namespace Tgstation.Server.Host.Security /// Create a for a given /// /// The to create the token for. Must have the field available + /// The representing the time the token expires /// A new - Token CreateToken(Models.User user); + Token CreateToken(Models.User user, out DateTimeOffset expiry); } } diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs new file mode 100644 index 0000000000..931f8f171c --- /dev/null +++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Security +{ + /// + /// For keeping a specific alive for a period of time + /// + sealed class IdentityCache : IDisposable + { + /// + /// The the manages + /// + public ISystemIdentity SystemIdentity { get; } + + /// + /// The for the + /// + readonly CancellationTokenSource cancellationTokenSource; + + /// + /// The to clean up + /// + readonly Task task; + + /// + /// Construct an + /// + /// The value of + /// The + /// An optional to take on expiry + public IdentityCache(ISystemIdentity systemIdentity, DateTimeOffset expiry, Action onExpiry) + { + SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity)); + + cancellationTokenSource = new CancellationTokenSource(); + + async Task DisposeOnExipiry(CancellationToken cancellationToken) + { + using (SystemIdentity) + try + { + await Task.Delay(expiry - DateTimeOffset.Now, cancellationToken).ConfigureAwait(false); + } + finally + { + onExpiry?.Invoke(); + } + } + + task = DisposeOnExipiry(cancellationTokenSource.Token); + } + + /// + public void Dispose() + { + cancellationTokenSource.Cancel(); + task.Wait(); + cancellationTokenSource.Dispose(); + } + } +} diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index e0e4c9af7b..47b3809afe 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Security.Claims; -using System.Text; using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Security @@ -12,20 +11,26 @@ namespace Tgstation.Server.Host.Security /// sealed class TokenFactory : ITokenFactory { + /// + /// Amount of hours until generated s expire + /// + const int TokenExpiryHours = 1; + public static readonly string TokenAudience = typeof(Token).Assembly.GetName().Name; public static readonly string TokenIssuer = Assembly.GetExecutingAssembly().GetName().Name; public static readonly byte[] TokenSigningKey = CryptographySuite.GetSecureBytes(256); /// - public Token CreateToken(Models.User user) + public Token CreateToken(Models.User user, out DateTimeOffset expiry) { if (user == null) throw new ArgumentNullException(nameof(user)); + expiry = DateTimeOffset.Now.AddHours(TokenExpiryHours); var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString(CultureInfo.InvariantCulture)), - new Claim(JwtRegisteredClaimNames.Exp, $"{DateTimeOffset.Now.AddHours(1).ToUnixTimeSeconds()}"), + new Claim(JwtRegisteredClaimNames.Exp, $"{expiry.ToUnixTimeSeconds()}"), new Claim(JwtRegisteredClaimNames.Nbf, $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"), new Claim(JwtRegisteredClaimNames.Iss, TokenIssuer), new Claim(JwtRegisteredClaimNames.Aud, TokenAudience) diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs index 626d699ac7..1e0dc1afcf 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs @@ -1,4 +1,5 @@ using System; +using System.DirectoryServices.AccountManagement; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; @@ -16,7 +17,12 @@ namespace Tgstation.Server.Host.Security readonly WindowsIdentity identity; /// - /// Construct a + /// The for the + /// + readonly UserPrincipal userPrincipal; + + /// + /// Construct a using a /// /// The value of public WindowsSystemIdentity(WindowsIdentity identity) @@ -24,23 +30,40 @@ namespace Tgstation.Server.Host.Security this.identity = identity ?? throw new ArgumentNullException(nameof(identity)); } + /// + /// Construct a using a + /// + /// The value of + public WindowsSystemIdentity(UserPrincipal userPrincipal) + { + this.userPrincipal = userPrincipal ?? throw new ArgumentNullException(nameof(userPrincipal)); + } + /// public void Dispose() => identity.Dispose(); /// - public string Uid => identity.User.ToString(); + public string Uid => (userPrincipal?.Sid ?? identity.User).ToString(); /// - public string Username => identity.Name; + public string Username => userPrincipal?.Name ?? identity.Name; /// - public ISystemIdentity Clone() => new WindowsSystemIdentity((WindowsIdentity)identity.Clone()); + public ISystemIdentity Clone() + { + if (identity != null) + return new WindowsSystemIdentity((WindowsIdentity)identity.Clone()); + //can't clone a UP, shouldn't be trying to anyway, cloning is for impersonation + throw new InvalidOperationException("Cannot clone a UserPrincipal based WindowsSystemIdentity!"); + } /// public Task RunImpersonated(Action action, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { if (action == null) throw new ArgumentNullException(nameof(action)); + if (identity == null) + throw new InvalidOperationException("Impersonate using a UserPrincipal based WindowsSystemIdentity!"); WindowsIdentity.RunImpersonated(identity.AccessToken, action); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs index 286d2daaec..781f0b381e 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs @@ -1,7 +1,7 @@ using Microsoft.Win32.SafeHandles; using System; using System.ComponentModel; -using System.Globalization; +using System.DirectoryServices.AccountManagement; using System.Runtime.InteropServices; using System.Security.Principal; using System.Threading; @@ -24,15 +24,19 @@ namespace Tgstation.Server.Host.Security if (user.SystemIdentifier == null) throw new InvalidOperationException("User's SystemIdentifier must not be null!"); - //System identity at this point will always be in the form DOMAIN\\USER or USER - var splits = user.SystemIdentifier.Split('\\'); - string identity; - if (splits.Length > 1) - identity = String.Format(CultureInfo.InvariantCulture, "{0}@{1}", splits[0], splits[1]); - else - identity = user.SystemIdentifier; + UserPrincipal principal = null; + //machine logon first cause it's faster + using (var pc = new PrincipalContext(ContextType.Machine, Environment.UserDomainName)) + principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier); - return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(identity)); + if(principal == null) + using (var pc = new PrincipalContext(ContextType.Domain, Environment.UserDomainName)) + principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier); + + if (principal == null) + return null; + + return (ISystemIdentity)new WindowsSystemIdentity(principal); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index ab2cb8874e..c49fbbad8b 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -87,9 +87,19 @@ namespace Tgstation.Server.Host } /// - public void RegisterForUpdate(Action action) => cancellationTokenSource.Token.Register(action); + public void RegisterForUpdate(Action action) + { + if (cancellationTokenSource == null) + throw new InvalidOperationException("Tried to register an update action on a non-running Server!"); + cancellationTokenSource.Token.Register(action); + } /// - public void Restart() => cancellationTokenSource.Cancel(); + public void Restart() + { + if (cancellationTokenSource == null) + throw new InvalidOperationException("Tried to restart a non-running Server!"); + cancellationTokenSource.Cancel(); + } } } From 0dd42616fde69c9ff8d431a29815cb2744f92190 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 26 Jul 2018 17:37:29 -0400 Subject: [PATCH 331/397] Posix system identifiers blocked by https://github.com/dotnet/corefx/issues/3187 --- src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs index 659fe36260..390f5104b6 100644 --- a/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs @@ -8,6 +8,7 @@ namespace Tgstation.Server.Host.Security /// /// for posix systems /// + /// Blocked by https://github.com/dotnet/corefx/issues/3187 sealed class PosixSystemIdentityFactory : ISystemIdentityFactory { /// From d2d201bd53bda16cedacf675c90d34152328b45d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 26 Jul 2018 17:50:54 -0400 Subject: [PATCH 332/397] Finishes the instance contruction process --- .../Components/IDmbFactory.cs | 3 +- .../Components/IRepositoryManager.cs | 2 +- .../Components/Instance.cs | 9 ++- .../Components/InstanceFactory.cs | 68 ++++++++++++++----- .../Components/RepositoryManager.cs | 2 +- 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/IDmbFactory.cs index 378c9d9860..bcdd48907a 100644 --- a/src/Tgstation.Server.Host/Components/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/IDmbFactory.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Models; diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs index 94fd3a2ac3..ac2823b33a 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Components /// /// Factory for creating and loading s /// - public interface IRepositoryManager + public interface IRepositoryManager : IDisposable { /// /// Attempt to load the from the default location diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 8c4b30f05c..b886dbee45 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -74,7 +74,14 @@ namespace Tgstation.Server.Host.Components } /// - public void Dispose() => timerCts?.Dispose(); + public void Dispose() + { + timerCts?.Dispose(); + Watchdog.Dispose(); + Chat.Dispose(); + RepositoryManager.Dispose(); + compileJobConsumer.Dispose(); + } /// /// Pull the repository and compile for every set of given diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index ec9021ff1d..6e67f0ff26 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -68,6 +68,11 @@ namespace Tgstation.Server.Host.Components /// readonly ISymlinkFactory symlinkFactory; + /// + /// The for the + /// + readonly IByondInstaller byondInstaller; + /// /// Construct an /// @@ -82,7 +87,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -95,6 +101,7 @@ namespace Tgstation.Server.Host.Components this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); + this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller)); } /// @@ -110,24 +117,53 @@ namespace Tgstation.Server.Host.Components var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, metadata); - var commandFactory = new CommandFactory(application); - var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory); + try + { + var commandFactory = new CommandFactory(application); + var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory); - var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); + var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); + try + { + var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger()); + var configuration = new Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); - IByondManager byond = null; - var configuration = new Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); - - var chat = chatFactory.CreateChat(); - var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata); - var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata); - var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, metadata); - var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings); + var chat = chatFactory.CreateChat(); + try + { + var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata); + var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata); + var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, metadata); + var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings); + try + { + var dreamMaker = new DreamMaker(byond, ioManager, configuration, sessionControllerFactory, dmbFactory, application, watchdog, loggerFactory.CreateLogger()); - var dreamMaker = new DreamMaker(byond, ioManager, configuration, sessionControllerFactory, dmbFactory, application, watchdog, loggerFactory.CreateLogger()); - - throw new NotImplementedException(); - //return new Instance(metadata, repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory); + return new Instance(metadata, repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory); + } + catch + { + watchdog.Dispose(); + throw; + } + } + catch + { + chat.Dispose(); + throw; + } + } + catch + { + repoManager.Dispose(); + throw; + } + } + catch + { + dmbFactory.Dispose(); + throw; + } } } } diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index e18bd24820..118509530a 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -8,7 +8,7 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { /// - sealed class RepositoryManager : IRepositoryManager, IDisposable + sealed class RepositoryManager : IRepositoryManager { /// /// The for the From f97056bb82ecd0d198fa4ce5f1c65ca62d117cf6 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 26 Jul 2018 18:21:58 -0400 Subject: [PATCH 333/397] Fix build --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 566210beea..15541bc685 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -63,6 +63,9 @@ after_test: - ps: Remove-Item artifacts/ServerHost/appsettings.Docker.json - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerService/lib/Default -recurse - ps: ls artifacts/ServerService + #I have literally no idea how these jsons got here + - ps: Remove-Item -path artifacts/ServerService/appsettings.json + - ps: Remove-Item -path artifacts/ServerService/appsettings..Docker.json - ps: Move-Item -path artifacts/ServerService/lib/Default/appsettings.json -destination artifacts/ServerService/ - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerConsole/lib/Default -recurse - ps: Move-Item -path artifacts/ServerConsole/lib/Default/appsettings.json -destination artifacts/ServerConsole/ From f299eba4699ee498ffb52db6fa804bcb433fd7fe Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 26 Jul 2018 21:25:38 -0400 Subject: [PATCH 334/397] Fixups tested users controller --- .../Controllers/AdministrationController.cs | 2 +- .../Controllers/ApiController.cs | 12 +- .../Controllers/ChatController.cs | 2 +- .../Controllers/ConfigurationController.cs | 2 +- .../Controllers/DreamDaemonController.cs | 2 +- .../Controllers/DreamMakerController.cs | 2 +- .../Controllers/HomeController.cs | 14 +- .../Controllers/InstanceController.cs | 2 +- .../Controllers/InstanceUserController.cs | 2 +- .../Controllers/InteropController.cs | 2 +- .../Controllers/JobController.cs | 2 +- .../Controllers/ModelController.cs | 7 +- .../Controllers/UserController.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 6 +- .../Security/AuthenticationContextFactory.cs | 71 +-- .../Security/IAuthenticationContextFactory.cs | 8 - .../Security/IIdentityCache.cs | 23 + .../Security/IdentityCache.cs | 90 ++-- .../Security/IdentityCacheObject.cs | 68 +++ .../Security/WindowsSystemIdentity.cs | 8 +- .../Security/WindowsSystemIdentityFactory.cs | 24 +- tools/TGS.postman_collection.json | 491 +++++++++++++++++- 22 files changed, 695 insertions(+), 147 deletions(-) create mode 100644 src/Tgstation.Server.Host/Security/IIdentityCache.cs create mode 100644 src/Tgstation.Server.Host/Security/IdentityCacheObject.cs diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index cd966e7b6d..e8d8ca75dc 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The containing value of - public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory) + public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 30d503f871..f835f6d004 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -43,6 +43,11 @@ namespace Tgstation.Server.Host.Controllers /// protected Instance Instance { get; } + /// + /// If permissions are required to access the + /// + readonly bool requireInstance; + /// /// Runs after a has been validated. Creates the for the /// @@ -81,6 +86,7 @@ namespace Tgstation.Server.Host.Controllers await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.HttpContext.RequestAborted).ConfigureAwait(false); + var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; var enumerator = Enum.GetValues(typeof(RightsType)); @@ -103,19 +109,21 @@ namespace Tgstation.Server.Host.Controllers /// /// The value of /// The for the - public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) + /// The value of + public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, bool requireInstance) { DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); if (authenticationContextFactory == null) throw new ArgumentNullException(nameof(authenticationContextFactory)); AuthenticationContext = authenticationContextFactory.CurrentAuthenticationContext; Instance = AuthenticationContext?.InstanceUser?.Instance; + this.requireInstance = requireInstance; } /// public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - if (AuthenticationContext == null) + if (requireInstance && AuthenticationContext.InstanceUser == null) { //accessing an instance they don't have access to await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 6cda73d094..195745ea11 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -34,7 +34,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public ChatController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) + public ChatController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); } diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index fb3abd7ba4..903d08eee9 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The value of /// The value of - public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory) + public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, true) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index b5574d49c7..0167d49112 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -39,7 +39,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The value of /// The value of - public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) + public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 3437886044..f07b232017 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -36,7 +36,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The value of /// The value of - public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) + public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 414593875e..9ce8efffae 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -35,9 +35,9 @@ namespace Tgstation.Server.Host.Controllers /// readonly IApplication application; /// - /// The for the + /// The for the /// - readonly IAuthenticationContextFactory authenticationContextFactory; + readonly IIdentityCache identityCache; /// /// Construct a @@ -48,14 +48,14 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application) : base(databaseContext, authenticationContextFactory) + /// The value of + public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache) : base(databaseContext, authenticationContextFactory, false) { this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.application = application ?? throw new ArgumentNullException(nameof(application)); - //base checks not null - this.authenticationContextFactory = authenticationContextFactory; + this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache)); } /// @@ -109,7 +109,7 @@ namespace Tgstation.Server.Host.Controllers else try { - identity = await systemIdentityFactory.CreateSystemIdentity(user.Name, ApiHeaders.Password, cancellationToken).ConfigureAwait(false); + identity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false); if (identity == null || identity.Uid != user.SystemIdentifier) return Unauthorized(); } @@ -123,7 +123,7 @@ namespace Tgstation.Server.Host.Controllers var token = tokenFactory.CreateToken(user, out var expiry); if (identity != null) - authenticationContextFactory.CacheSystemIdentity(user, identity, expiry.AddSeconds(10)); //expire the identity slightly after the auth token in case of lag + identityCache.CacheSystemIdentity(user, identity, expiry.AddSeconds(10)); //expire the identity slightly after the auth token in case of lag return Json(token); } } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index e7f38e8a01..aa4a656911 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -42,7 +42,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager) : base(databaseContext, authenticationContextFactory) + public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager) : base(databaseContext, authenticationContextFactory, false) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs index 9ddc1d43cc..ceb71bd530 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -31,7 +31,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory) + public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, false) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } diff --git a/src/Tgstation.Server.Host/Controllers/InteropController.cs b/src/Tgstation.Server.Host/Controllers/InteropController.cs index 2f1690884d..d145f99632 100644 --- a/src/Tgstation.Server.Host/Controllers/InteropController.cs +++ b/src/Tgstation.Server.Host/Controllers/InteropController.cs @@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public InteropController(IInstanceManager instanceManager, IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory) + public InteropController(IInstanceManager instanceManager, IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory, false) { this.instanceManager = instanceManager; } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 8219125d29..c7bab7aee3 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) + public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory, false) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); } diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 7943e4ab91..69680526fe 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -24,7 +24,8 @@ namespace Tgstation.Server.Host.Controllers /// /// The for the /// The for the - public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory) { } + /// If the requires an + public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, bool requireInstance) : base(databaseContext, authenticationContextFactory, requireInstance) { } /// /// Attempt to create a @@ -49,7 +50,7 @@ namespace Tgstation.Server.Host.Controllers /// The ID of the model to get /// The for the operation /// A resulting in the of the operation - [HttpGet("/{id}")] + [HttpGet("{id}")] public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// @@ -75,7 +76,7 @@ namespace Tgstation.Server.Host.Controllers /// /// The for the operation /// A resulting in the of the operation - [HttpGet("/List")] + [HttpGet("List")] public virtual Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); } } diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 376ccd3b26..25bd803e95 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - public UserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory) + public UserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory, false) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index c4f285701f..b70dd81236 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -148,9 +148,9 @@ namespace Tgstation.Server.Host.Core default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}!", nameof(DatabaseType))); } - - //very important this remains a singleton - services.AddSingleton(); + + services.AddScoped(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 3bddfce971..819e6471f3 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -10,7 +10,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { /// - sealed class AuthenticationContextFactory : IAuthenticationContextFactory, IDisposable + sealed class AuthenticationContextFactory : IAuthenticationContextFactory { /// public IAuthenticationContext CurrentAuthenticationContext { get; private set; } @@ -21,53 +21,22 @@ namespace Tgstation.Server.Host.Security readonly ISystemIdentityFactory systemIdentityFactory; /// - /// The for the + /// The for the /// - readonly IDatabaseContextFactory databaseContextFactory; + readonly IDatabaseContext databaseContext; - /// - /// Map of s to s for that user - /// - readonly Dictionary identityCache; + readonly IIdentityCache identityCache; /// /// Construct an /// /// The value of /// The value of - public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContextFactory databaseContextFactory) + public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContext databaseContext, IIdentityCache identityCache) { this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); - this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); - - identityCache = new Dictionary(); - } - - /// - public void Dispose() - { - foreach (var I in identityCache) - I.Value.Dispose(); - } - - /// - public void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry) - { - if (user == null) - throw new ArgumentNullException(nameof(user)); - if (systemIdentity == null) - throw new ArgumentNullException(nameof(systemIdentity)); - lock (identityCache) - { - if (identityCache.TryGetValue(user.Id, out var identCache)) - identCache.Dispose(); //also clears it out - identCache = new IdentityCache(systemIdentity.Clone(), expiry, () => - { - lock (identityCache) - identityCache.Remove(user.Id); - }); - identityCache.Add(user.Id, identCache); - } + this.databaseContext = databaseContext?? throw new ArgumentNullException(nameof(databaseContext)); + this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache)); } /// @@ -76,32 +45,24 @@ namespace Tgstation.Server.Host.Security if (CurrentAuthenticationContext != null) throw new InvalidOperationException("Authentication context has already been loaded"); - User user = null; - await databaseContextFactory.UseContext(async db => - { - var userQuery = db.Users.Where(x => x.Id == userId); + var userQuery = databaseContext.Users.Where(x => x.Id == userId); - if (instanceId.HasValue) - userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId)); + if (instanceId.HasValue) + userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId)); - user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false); - }).ConfigureAwait(false); + var user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false); InstanceUser instanceUser = null; if (instanceId.HasValue) instanceUser = user.InstanceUsers.Where(x => x.InstanceId == instanceId).FirstOrDefault(); - if (instanceUser == default) - return; - ISystemIdentity systemIdentity; if (user.SystemIdentifier != null) - lock (identityCache) - { - if (!identityCache.TryGetValue(userId, out var identCache)) - throw new InvalidOperationException("Cached system identity has expired!"); - systemIdentity = identCache.SystemIdentity.Clone(); - } + { + systemIdentity = identityCache.LoadCachedIdentity(user); + if (systemIdentity == null) + throw new InvalidOperationException("Cached system identity has expired!"); + } else systemIdentity = null; diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs index 2e8552c037..121a9cc46b 100644 --- a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs @@ -15,14 +15,6 @@ namespace Tgstation.Server.Host.Security /// IAuthenticationContext CurrentAuthenticationContext { get; } - /// - /// Keep a 's alive until an time - /// - /// The the belongs to - /// The to cache - /// When the should expire - void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry); - /// /// Create an to populate /// diff --git a/src/Tgstation.Server.Host/Security/IIdentityCache.cs b/src/Tgstation.Server.Host/Security/IIdentityCache.cs new file mode 100644 index 0000000000..902424f6d3 --- /dev/null +++ b/src/Tgstation.Server.Host/Security/IIdentityCache.cs @@ -0,0 +1,23 @@ +using System; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Security +{ + public interface IIdentityCache + { + /// + /// Keep a 's alive until an time + /// + /// The the belongs to + /// The to cache + /// When the should expire + void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry); + + /// + /// Attempt to load a cached + /// + /// The the belongs to + /// The cached or if it doesn't exist or expired + ISystemIdentity LoadCachedIdentity(User user); + } +} diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs index 931f8f171c..81850ab4f2 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs @@ -1,63 +1,57 @@ using System; -using System.Threading; -using System.Threading.Tasks; +using System.Collections.Generic; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { - /// - /// For keeping a specific alive for a period of time - /// - sealed class IdentityCache : IDisposable + /// + sealed class IdentityCache : IIdentityCache, IDisposable { - /// - /// The the manages - /// - public ISystemIdentity SystemIdentity { get; } + readonly Dictionary cachedIdentities; - /// - /// The for the - /// - readonly CancellationTokenSource cancellationTokenSource; - - /// - /// The to clean up - /// - readonly Task task; - - /// - /// Construct an - /// - /// The value of - /// The - /// An optional to take on expiry - public IdentityCache(ISystemIdentity systemIdentity, DateTimeOffset expiry, Action onExpiry) + public IdentityCache() { - SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity)); - - cancellationTokenSource = new CancellationTokenSource(); - - async Task DisposeOnExipiry(CancellationToken cancellationToken) - { - using (SystemIdentity) - try - { - await Task.Delay(expiry - DateTimeOffset.Now, cancellationToken).ConfigureAwait(false); - } - finally - { - onExpiry?.Invoke(); - } - } - - task = DisposeOnExipiry(cancellationTokenSource.Token); + cachedIdentities = new Dictionary(); } /// public void Dispose() { - cancellationTokenSource.Cancel(); - task.Wait(); - cancellationTokenSource.Dispose(); + foreach (var I in cachedIdentities) + I.Value.Dispose(); + } + + /// + public void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + if (systemIdentity == null) + throw new ArgumentNullException(nameof(systemIdentity)); + lock (cachedIdentities) + { + if (cachedIdentities.TryGetValue(user.Id, out var identCache)) + identCache.Dispose(); //also clears it out + identCache = new IdentityCacheObject(systemIdentity.Clone(), () => + { + lock (cachedIdentities) + cachedIdentities.Remove(user.Id); + }, expiry); + cachedIdentities.Add(user.Id, identCache); + } + } + + /// + public ISystemIdentity LoadCachedIdentity(User user) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + lock (cachedIdentities) + { + if (cachedIdentities.TryGetValue(user.Id, out var identity)) + return identity.SystemIdentity; + return null; + } } } } diff --git a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs new file mode 100644 index 0000000000..310c68b34c --- /dev/null +++ b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Security +{ + /// + /// For keeping a specific alive for a period of time + /// + sealed class IdentityCacheObject : IDisposable + { + /// + /// The the manages + /// + public ISystemIdentity SystemIdentity { get; } + + /// + /// The for the + /// + readonly CancellationTokenSource cancellationTokenSource; + + /// + /// The to clean up + /// + readonly Task task; + + /// + /// Construct an + /// + /// The value of + /// The to take on expiry + /// The + public IdentityCacheObject(ISystemIdentity systemIdentity, Action onExpiry, DateTimeOffset expiry) + { + SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity)); + if (onExpiry == null) + throw new ArgumentNullException(nameof(onExpiry)); + var now = DateTimeOffset.Now; + if (expiry < now) + throw new ArgumentOutOfRangeException(nameof(expiry), expiry, "expiry must be greater than DateTimeOffset.Now!"); + + cancellationTokenSource = new CancellationTokenSource(); + + async Task DisposeOnExipiry(CancellationToken cancellationToken) + { + using (SystemIdentity) + try + { + await Task.Delay(expiry - now, cancellationToken).ConfigureAwait(false); + } + finally + { + onExpiry(); + } + } + + task = DisposeOnExipiry(cancellationTokenSource.Token); + } + + /// + public void Dispose() + { + cancellationTokenSource.Cancel(); + task.Wait(); + cancellationTokenSource.Dispose(); + } + } +} diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs index 1e0dc1afcf..6f1ab56cba 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs @@ -40,7 +40,13 @@ namespace Tgstation.Server.Host.Security } /// - public void Dispose() => identity.Dispose(); + public void Dispose() + { + if (identity != null) + identity.Dispose(); + else + userPrincipal.Dispose(); + } /// public string Uid => (userPrincipal?.Sid ?? identity.User).ToString(); diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs index 781f0b381e..871269f3ae 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs @@ -24,14 +24,28 @@ namespace Tgstation.Server.Host.Security if (user.SystemIdentifier == null) throw new InvalidOperationException("User's SystemIdentifier must not be null!"); + PrincipalContext pc = null; UserPrincipal principal = null; //machine logon first cause it's faster - using (var pc = new PrincipalContext(ContextType.Machine, Environment.UserDomainName)) + try + { + pc = new PrincipalContext(ContextType.Machine, Environment.UserDomainName); principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier); - if(principal == null) - using (var pc = new PrincipalContext(ContextType.Domain, Environment.UserDomainName)) + if (principal == null) + { + pc.Dispose(); + pc = new PrincipalContext(ContextType.Domain, Environment.UserDomainName); principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier); + if (principal == null) + pc.Dispose(); + } + } + catch + { + pc?.Dispose(); + throw; + } if (principal == null) return null; @@ -42,6 +56,10 @@ namespace Tgstation.Server.Host.Security /// public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { + if (username == null) + throw new ArgumentNullException(nameof(username)); + if (password == null) + throw new ArgumentNullException(nameof(password)); var splits = username.Split('\\'); var res = NativeMethods.LogonUser(splits.Length > 1 ? splits[1] : splits[0], splits.Length > 1 ? splits[0] : null, password, 3 /*LOGON32_LOGON_NETWORK*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out var token); diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index 59bebb834f..b48b57f3d2 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -1,13 +1,13 @@ { "info": { - "_postman_id": "f0940866-29fd-4ea3-8682-7e30df5b08c9", + "_postman_id": "e62d1230-5b76-45e2-9fb6-002e260daa2a", "name": "TGS", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "Administration", - "description": "", + "description": null, "item": [ { "name": "Get Update Versions", @@ -27,7 +27,10 @@ "value": "Tgstation.Server.Api/4.0.0.0" } ], - "body": {}, + "body": { + "mode": "raw", + "raw": "" + }, "url": { "raw": "localhost:5000/Administration", "host": [ @@ -98,7 +101,10 @@ "value": "Tgstation.Server.Api/4.0.0.0" } ], - "body": {}, + "body": { + "mode": "raw", + "raw": "" + }, "url": { "raw": "localhost:5000/Administration", "host": [ @@ -136,6 +142,475 @@ } ] }, + { + "name": "Users", + "description": "", + "item": [ + { + "name": "Read Current User", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + } + ], + "body": {}, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Read All Users", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + } + ], + "body": {}, + "url": { + "raw": "localhost:5000/User/List", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User", + "List" + ] + } + }, + "response": [] + }, + { + "name": "Create User Bob Pass (asdf)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 1,\n \"enabled\": true,\n \"password\": \"asdf\",\n \"name\": \"Bob\"\n}" + }, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Try To Add A SysId To User ID 2", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 2,\n \"systemIdentifier\": \"fake\"\n}" + }, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Enable User ID 3", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 3,\n \"enabled\": true\n}" + }, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Try To Add A Password To User ID", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 3,\n \"password\": \"fake\"\n}" + }, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Change User ID 2's password and name to bOb and fdsa", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 2,\n \"password\": \"fdsa\",\n \"name\": \"bOb\"\n}" + }, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Change User ID 2's name to Bob2", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 2,\n \"name\": \"Bob2\"\n}" + }, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Give User ID 3 rights", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 3,\n \"administrationRights\": -1,\n\t\"instanceManagerRights\": -1,\n}" + }, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Create system user Cyberboss", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"systemIdentifier\": \"Cyberboss\"\n}" + }, + "url": { + "raw": "localhost:5000/User", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "User" + ] + } + }, + "response": [] + }, + { + "name": "Login bob (asdf)", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Authorization", + "value": "Password asdf" + }, + { + "key": "Username", + "value": "bob" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "localhost:5000", + "host": [ + "localhost" + ], + "port": "5000" + } + }, + "response": [] + }, + { + "name": "Login cyberboss", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Authorization", + "value": "Password no" + }, + { + "key": "Username", + "value": "cyberboss" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "localhost:5000", + "host": [ + "localhost" + ], + "port": "5000" + } + }, + "response": [] + } + ] + }, { "name": "Login Admin Default", "request": { @@ -165,7 +640,10 @@ "value": "admin" } ], - "body": {}, + "body": { + "mode": "raw", + "raw": "" + }, "url": { "raw": "localhost:5000", "host": [ @@ -200,7 +678,6 @@ { "key": "name", "value": "asdfasdf", - "description": "", "type": "text" } ] @@ -221,7 +698,7 @@ "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjU0MjgwMyIsIm5iZiI6IjE1MzI1MzkyMDMiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.f-y5xQ-JbZklD7iMeztZhLGfXBWnSxxlYtzl3TXDt5g", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjY1ODI0OSIsIm5iZiI6IjE1MzI2NTQ2NDkiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.nfVqGC501-l1hkXCn6NsLj-rLFBVU65feHKLxXV3X8c", "type": "string" } ] From 01828a668f1b6e97eb224e66a052ab0a2e3e8721 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 27 Jul 2018 08:56:22 -0400 Subject: [PATCH 335/397] WIP Db cascade refactoring. Issue with RevInfo/CompileJob cascade. Consider deleting compile jobs manually --- .../Components/DmbFactory.cs | 2 +- .../Components/Instance.cs | 2 +- .../Controllers/InstanceController.cs | 106 ++++++++++++++++-- .../IO/DefaultIOManager.cs | 10 ++ src/Tgstation.Server.Host/IO/IIOManager.cs | 9 ++ .../Models/CompileJob.cs | 3 +- .../Models/DatabaseContext.cs | 36 +++--- .../Models/DreamMakerSettings.cs | 5 - .../Models/IDatabaseContext.cs | 10 ++ src/Tgstation.Server.Host/Models/Instance.cs | 15 +-- src/Tgstation.Server.Host/Models/Job.cs | 3 +- .../Models/ReattachInformation.cs | 2 +- .../Models/RepositorySettings.cs | 5 - .../Models/RevisionInformation.cs | 10 ++ src/Tgstation.Server.Host/Models/TestMerge.cs | 2 +- src/Tgstation.Server.Host/Models/User.cs | 6 +- 16 files changed, 178 insertions(+), 48 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index b6aed13e2b..f845e58369 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -152,7 +152,7 @@ namespace Tgstation.Server.Host.Components public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { //where complete clause not necessary, only successful COMPILEjobs get in the db - var cj = await db.Instances.Where(x => x.Id == instanceId).SelectMany(x => x.CompileJobs).OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var cj = await db.Instances.Where(x => x.Id == instanceId).SelectMany(x => x.RevisionInformations).SelectMany(x => x.CompileJobs).OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (cj == default(CompileJob)) return; var directoriesTask = ioManager.GetDirectories(".", cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index b886dbee45..4c58438157 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Components await dbTask.ConfigureAwait(false); await repo.FetchOrigin(accessToken, cancellationToken).ConfigureAwait(false); await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); - await DreamMaker.Compile(projectName, timeout, repo, cancellationToken).ConfigureAwait(false); + var job = await DreamMaker.Compile(projectName, timeout, repo, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index aa4a656911..3b72511687 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -2,10 +2,13 @@ using Microsoft.EntityFrameworkCore; using System; using System.Linq; +using System.Linq.Expressions; using System.Net; +using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Core; @@ -53,14 +56,21 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(InstanceManagerRights.Create)] public override async Task Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) { + + if (String.IsNullOrWhiteSpace(model.Name)) + return BadRequest(new { message = "name must not be empty!" }); + + if(model.Path == null) + return BadRequest(new { message = "path must not be empty!" }); + + model.Path = ioManager.ResolvePath(model.Path); var dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken); if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false)) return Conflict(new { message = "Path not empty!" }); var newInstance = new Models.Instance { - ChatSettings = new ChatSettings(), - ConfigurationType = model.ConfigurationType, + ConfigurationType = model.ConfigurationType ?? ConfigurationType.Disallowed, DreamDaemonSettings = new DreamDaemonSettings(), DreamMakerSettings = new DreamMakerSettings(), Name = model.Name, @@ -83,10 +93,6 @@ namespace Tgstation.Server.Host.Controllers { //oh shit delete the model DatabaseContext.Instances.Remove(newInstance); - DatabaseContext.DreamMakerSettings.Remove(newInstance.DreamMakerSettings); - DatabaseContext.ChatSettings.Remove(newInstance.ChatSettings); - DatabaseContext.RepositorySettings.Remove(newInstance.RepositorySettings); - DatabaseContext.DreamDaemonSettings.Remove(newInstance.DreamDaemonSettings); await DatabaseContext.Save(default).ConfigureAwait(false); @@ -101,6 +107,31 @@ namespace Tgstation.Server.Host.Controllers return Json(newInstance.ToApi()); } + [TgsAuthorize(InstanceManagerRights.Delete)] + public override async Task Delete(long id, CancellationToken cancellationToken) + { + var originalModel = await DatabaseContext.Instances.Where(x => x.Id == id) + .Include(x => x.WatchdogReattachInformation) + .Include(x => x.WatchdogReattachInformation.Alpha) + .Include(x => x.WatchdogReattachInformation.Bravo) + .FirstAsync(cancellationToken).ConfigureAwait(false); + if (originalModel == default(Models.Instance)) + return StatusCode((int)HttpStatusCode.Gone); + + if (originalModel.WatchdogReattachInformation != null) + { + DatabaseContext.WatchdogReattachInformations.Remove(originalModel.WatchdogReattachInformation); + if (originalModel.WatchdogReattachInformation.Alpha != null) + DatabaseContext.ReattachInformations.Remove(originalModel.WatchdogReattachInformation.Alpha); + if (originalModel.WatchdogReattachInformation.Bravo != null) + DatabaseContext.ReattachInformations.Remove(originalModel.WatchdogReattachInformation.Bravo); + } + + DatabaseContext.Instances.Remove(originalModel); + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); //cascades everything + return Ok(); + } + /// [TgsAuthorize(InstanceManagerRights.Relocate | InstanceManagerRights.Rename | InstanceManagerRights.SetAutoUpdate | InstanceManagerRights.SetConfiguration | InstanceManagerRights.SetOnline)] public override async Task Update([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) @@ -109,7 +140,68 @@ namespace Tgstation.Server.Host.Controllers if (originalModel == default(Models.Instance)) return StatusCode((int)HttpStatusCode.Gone); - throw new NotImplementedException(); + var userRights = (InstanceManagerRights)AuthenticationContext.GetRight(RightsType.InstanceManager); + bool CheckModified(Expression> expression, InstanceManagerRights requiredRight) + { + var memberSelectorExpression = (MemberExpression)expression.Body; + var property = (PropertyInfo)memberSelectorExpression.Member; + + var newVal = property.GetValue(model); + if (newVal == null) + return false; + if (!userRights.HasFlag(requiredRight) && property.GetValue(originalModel) != newVal) + return true; + + property.SetValue(originalModel, newVal); + return false; + }; + + string originalModelPath = null; + if (model.Path != null) + { + model.Path = ioManager.ResolvePath(model.Path); + model.Path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? model.Path.ToUpperInvariant() : model.Path; + + if (model.Path != originalModel.Path) + { + if (!userRights.HasFlag(InstanceManagerRights.Relocate)) + return Forbid(); + if (originalModel.Online.Value && model.Online != true) + return Conflict(new { message = "Cannot relocate an online instance!" }); + + var dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken); + if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false)) + return Conflict(new { message = "Path not empty!" }); + + originalModelPath = originalModel.Path; + originalModel.Path = model.Path; + } + } + + if (CheckModified(x => x.AutoUpdateInterval, InstanceManagerRights.SetAutoUpdate) + || CheckModified(x => x.ConfigurationType, InstanceManagerRights.SetConfiguration) + || CheckModified(x => x.Name, InstanceManagerRights.Rename) + || CheckModified(x => x.Online, InstanceManagerRights.SetOnline)) + return Forbid(); + + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + + if (originalModel.Online.Value && model.Online.Value == false) + await instanceManager.OfflineInstance(originalModel, cancellationToken).ConfigureAwait(false); + else if(!originalModel.Online.Value && model.Online.Value == true) + await instanceManager.OnlineInstance(originalModel, cancellationToken).ConfigureAwait(false); + + if (originalModelPath != null) + await ioManager.MoveDirectory(originalModelPath, model.Path, cancellationToken).ConfigureAwait(false); + + return Json(originalModel.ToApi()); + } + + [TgsAuthorize] + public override async Task List(CancellationToken cancellationToken) + { + var instances = await DatabaseContext.Instances.Where(x => x.InstanceUsers.Any(y => y.UserId == AuthenticationContext.User.Id && y.AnyRights)).ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(instances); } } } diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 3b2addc55f..865411b1b6 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -192,6 +192,16 @@ namespace Tgstation.Server.Host.IO File.Move(source, destination); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + /// + public Task MoveDirectory(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source))); + destination = ResolvePath(destination); + Directory.Move(source, destination); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + /// public async Task ReadAllBytes(string path, CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index 3654fed7f5..da63c6d117 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -155,6 +155,15 @@ namespace Tgstation.Server.Host.IO /// A representing the running operation Task MoveFile(string source, string destination, CancellationToken cancellationToken); + /// + /// Moves a directory at to + /// + /// The source directory path + /// The destination path + /// A for the operation + /// A representing the running operation + Task MoveDirectory(string source, string destination, CancellationToken cancellationToken); + /// /// Downloads a file from /// diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index 1fbcc9f498..f6aa72488c 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { @@ -15,7 +14,6 @@ namespace Tgstation.Server.Host.Models /// /// See /// - [Required] public Job Job { get; set; } /// @@ -27,6 +25,7 @@ namespace Tgstation.Server.Host.Models /// /// The the was made with in string form /// + [Required] public string ByondVersion { get; set; } /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index c9bacb6086..32fd9843ec 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -59,15 +59,11 @@ namespace Tgstation.Server.Host.Models /// public DbSet TestMerges { get; set; } - /// - /// The for s - /// - public DbSet ReattachInformation { get; set; } + /// + public DbSet ReattachInformations { get; set; } - /// - /// The for s - /// - public DbSet WatchdogReattachInformation { get; set; } + /// + public DbSet WatchdogReattachInformations { get; set; } /// /// The connection string for the @@ -103,18 +99,32 @@ namespace Tgstation.Server.Host.Models // build default model. LogModelBuilderHelper.Build(modelBuilder.Entity()); modelBuilder.Entity().ToTable(nameof(Logs)); - modelBuilder.Entity().HasIndex(x => x.Path).IsUnique(); - modelBuilder.Entity().HasIndex(x => x.Commit).IsUnique(); - var user = modelBuilder.Entity(); - user.HasIndex(x => x.CanonicalName).IsUnique(); - user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict); + + var userModel = modelBuilder.Entity(); + userModel.HasIndex(x => x.CanonicalName).IsUnique(); + userModel.HasMany(x => x.TestMerges).WithOne(x => x.MergedBy).OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity().HasIndex(x => new { x.UserId, x.InstanceId }).IsUnique(); + var revInfo = modelBuilder.Entity(); + revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); + revInfo.HasMany(x => x.TestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); + revInfo.HasIndex(x => x.Commit).IsUnique(); + var chatChannel = modelBuilder.Entity(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique(); chatChannel.HasOne(x => x.ChatSettings).WithMany(x => x.Channels).HasForeignKey(x => x.ChatSettingsId).OnDelete(DeleteBehavior.Cascade); + + var instanceModel = modelBuilder.Entity(); + instanceModel.HasIndex(x => x.Path).IsUnique(); + instanceModel.HasMany(x => x.ChatSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); + instanceModel.HasOne(x => x.DreamDaemonSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); + instanceModel.HasOne(x => x.DreamMakerSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); + instanceModel.HasOne(x => x.RepositorySettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); + instanceModel.HasMany(x => x.RevisionInformations).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); + instanceModel.HasMany(x => x.InstanceUsers).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); + instanceModel.HasMany(x => x.Jobs).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade); } /// diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 286be820a2..f2dacbfc64 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -20,10 +20,5 @@ namespace Tgstation.Server.Host.Models /// [Required] public Instance Instance { get; set; } - - /// - /// - /// - public CompileJob LastJob { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 0d3814a284..2014075b58 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -59,6 +59,16 @@ namespace Tgstation.Server.Host.Models /// DbSet RepositorySettings { get; set; } + /// + /// The for s + /// + DbSet ReattachInformations { get; set; } + + /// + /// The for s + /// + DbSet WatchdogReattachInformations { get; set; } + /// /// Saves changes made to the /// diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index 4b6b57762c..10b2eb9ece 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { @@ -8,10 +7,6 @@ namespace Tgstation.Server.Host.Models /// public sealed class Instance : Api.Models.Instance, IApiConvertable { - /// - /// The for the - /// - public ChatSettings ChatSettings { get; set; } /// /// The for the @@ -39,15 +34,15 @@ namespace Tgstation.Server.Host.Models public List InstanceUsers { get; set; } /// - /// The s in the + /// The s for the /// - public List TestMerges { get; set; } + public List ChatSettings { get; set; } /// - /// The s in the + /// The s in the /// - public List CompileJobs { get; set; } - + public List RevisionInformations { get; set; } + /// /// The in the /// diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 1c0cb88d3c..4262ea1aff 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -17,8 +17,9 @@ namespace Tgstation.Server.Host.Models public User CancelledBy { get; set; } /// - /// The the job belongs to if any + /// The the job belongs to if any /// + [Required] public Instance Instance { get; set; } /// diff --git a/src/Tgstation.Server.Host/Models/ReattachInformation.cs b/src/Tgstation.Server.Host/Models/ReattachInformation.cs index cc2dcbdd06..90587941e7 100644 --- a/src/Tgstation.Server.Host/Models/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Models/ReattachInformation.cs @@ -14,5 +14,5 @@ /// The for the /// public CompileJob CompileJob { get; set; } - } + } } diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 77e529f2ec..3d42baf883 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -20,10 +20,5 @@ namespace Tgstation.Server.Host.Models /// [Required] public Instance Instance { get; set; } - - /// - /// See - /// - public RevisionInformation RevisionInformation { get; set; } } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 2d05122e66..1a9d2d3360 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -11,11 +11,21 @@ namespace Tgstation.Server.Host.Models ///
public long Id { get; set; } + /// + /// The the belongs to + /// + public Instance Instance { get; set; } + /// /// See /// public List TestMerges { get; set; } + /// + /// See s made from this + /// + public List CompileJobs { get; set; } + /// public Api.Models.RevisionInformation ToApi() => new Api.Models.RevisionInformation { diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index 108f584086..c4f6404f31 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { @@ -15,6 +14,7 @@ namespace Tgstation.Server.Host.Models /// /// The for the /// + [Required] public RevisionInformation RevisionInformation { get; set; } /// diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index eed7ccbba4..b30c01969e 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { @@ -31,6 +30,11 @@ namespace Tgstation.Server.Host.Models /// public List InstanceUsers { get; set; } + /// + /// The s made by the + /// + public List TestMerges { get; set; } + /// /// See /// From 3f558e01b8e7d40dd86307ac67b7a99b7686924d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 27 Jul 2018 08:57:00 -0400 Subject: [PATCH 336/397] Add a few instance postman tests --- tools/TGS.postman_collection.json | 86 ++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index b48b57f3d2..22d1a9d5b8 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -611,6 +611,90 @@ } ] }, + { + "name": "Instance", + "description": "", + "item": [ + { + "name": "Create Instance TestInstance", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Test Instance\",\n \"path\": \"C:\\\\TestInstance\"\n}" + }, + "url": { + "raw": "localhost:5000/Instance", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Instance" + ] + } + }, + "response": [] + }, + { + "name": "Create Instance invalid path", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Test Instance\",\n \"path\": \"C:\\\\Windows\"\n}" + }, + "url": { + "raw": "localhost:5000/Instance", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Instance" + ] + } + }, + "response": [] + } + ] + }, { "name": "Login Admin Default", "request": { @@ -698,7 +782,7 @@ "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjY1ODI0OSIsIm5iZiI6IjE1MzI2NTQ2NDkiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.nfVqGC501-l1hkXCn6NsLj-rLFBVU65feHKLxXV3X8c", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjY1ODQ4MiIsIm5iZiI6IjE1MzI2NTQ4ODIiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.GgcD2qfa5bA16w-lb2oEZLcieZhSGWg9wIv-OvhqGuA", "type": "string" } ] From 591416c92cb93b8882b52670a6a8938c19fe03b0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 10:00:00 -0400 Subject: [PATCH 337/397] Fix the build --- src/Tgstation.Server.Host/Controllers/HomeController.cs | 2 +- src/Tgstation.Server.Host/Controllers/InstanceController.cs | 2 ++ .../Security/AuthenticationContextFactory.cs | 6 +++++- src/Tgstation.Server.Host/Security/IIdentityCache.cs | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 9ce8efffae..c74858cf07 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - /// The value of + /// The value of public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache) : base(databaseContext, authenticationContextFactory, false) { this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 3b72511687..37ba9f674f 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -107,6 +107,7 @@ namespace Tgstation.Server.Host.Controllers return Json(newInstance.ToApi()); } + /// [TgsAuthorize(InstanceManagerRights.Delete)] public override async Task Delete(long id, CancellationToken cancellationToken) { @@ -197,6 +198,7 @@ namespace Tgstation.Server.Host.Controllers return Json(originalModel.ToApi()); } + /// [TgsAuthorize] public override async Task List(CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 819e6471f3..57b9349a4c 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -25,13 +25,17 @@ namespace Tgstation.Server.Host.Security /// readonly IDatabaseContext databaseContext; + /// + /// The for the + /// readonly IIdentityCache identityCache; /// /// Construct an /// /// The value of - /// The value of + /// The value of + /// The value of public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContext databaseContext, IIdentityCache identityCache) { this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); diff --git a/src/Tgstation.Server.Host/Security/IIdentityCache.cs b/src/Tgstation.Server.Host/Security/IIdentityCache.cs index 902424f6d3..7d6b2def11 100644 --- a/src/Tgstation.Server.Host/Security/IIdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IIdentityCache.cs @@ -3,6 +3,9 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { + /// + /// For caching s + /// public interface IIdentityCache { /// From 6cf06636c5cd70bad4e714d0b5ade3e251305849 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 10:01:04 -0400 Subject: [PATCH 338/397] Removes some unused usings --- .../Security/AuthenticationContextFactory.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 57b9349a4c..587530c022 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -1,10 +1,8 @@ using Microsoft.EntityFrameworkCore; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security From 4869b5e357edf5a9360d55ec15d72deb70318de6 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 10:46:06 -0400 Subject: [PATCH 339/397] Fix the db stuff --- src/Tgstation.Server.Api/Models/Internal/CompileJob.cs | 3 +-- .../Models/Internal/RevisionInformation.cs | 4 ++-- src/Tgstation.Server.Host/Components/DmbFactory.cs | 2 +- src/Tgstation.Server.Host/Components/IDreamMaker.cs | 2 +- .../Components/Watchdog/SessionControllerFactory.cs | 4 ++-- .../Controllers/DreamMakerController.cs | 2 +- .../Controllers/InstanceController.cs | 9 +++++++++ src/Tgstation.Server.Host/Models/CompileJob.cs | 5 ----- src/Tgstation.Server.Host/Models/DatabaseContext.cs | 2 +- src/Tgstation.Server.Host/Models/RevisionInformation.cs | 4 ++-- 10 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index 7f717c121c..f2863fd0b5 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal { @@ -16,7 +15,7 @@ namespace Tgstation.Server.Api.Models.Internal /// /// If the DMAPI version detected is compatible /// - public bool DMApiValidated { get; set; } + public bool? DMApiValidated { get; set; } /// /// The .dme file used for compilation diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs index a61788167f..159201a17d 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs @@ -12,13 +12,13 @@ namespace Tgstation.Server.Api.Models.Internal /// [Permissions(DenyWrite = true)] [Required, StringLength(40)] - public string Commit { get; set; } + public string CommitSha { get; set; } /// /// The sha of the most recent remote commit /// [Permissions(DenyWrite = true)] [Required, StringLength(40)] - public string OriginRevision { get; set; } + public string OriginCommitSha { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index f845e58369..28ace4fa08 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -106,7 +106,7 @@ namespace Tgstation.Server.Host.Components { if (job == null) throw new ArgumentNullException(nameof(job)); - if (!job.DMApiValidated || job.Job.Cancelled || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null) + if (job.DMApiValidated != true || job.Job.Cancelled || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null) throw new InvalidOperationException("Cannot load incomplete compile job!"); if (setAsStagedInDb) await databaseContextFactory.UseContext(async db => diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 55ebb664ff..d8610784cf 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Components /// The time in seconds to wait while validating the API /// The to copy from /// The for the operation - /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated + /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated Task Compile(string projectName, int apiValidateTimeout, IRepository repository, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 9b48f51f8d..4b29138ef0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -120,9 +120,9 @@ namespace Tgstation.Server.Host.Components.Watchdog Author = x.Author, Body = x.BodyAtMerge, Comment = x.Comment, - Commit = x.RevisionInformation.Commit, + CommitSha = x.RevisionInformation.CommitSha, Number = x.Number, - OriginRevision = x.RevisionInformation.OriginRevision, + OriginCommitSha = x.RevisionInformation.OriginCommitSha, PullRequestCommit = x.PullRequestRevision, TimeMerged = x.MergedAt.Ticks, Title = x.TitleAtMerge, diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index f07b232017..d00f4dabea 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -129,7 +129,7 @@ namespace Tgstation.Server.Host.Controllers Task revInfoTask; using (var repo = await instance.RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { - revInfoTask = databaseContext.RevisionInformations.Where(x => x.Commit == repo.Head).Select(x => new RevisionInformation { Id = x.Id }).FirstAsync(); + revInfoTask = databaseContext.RevisionInformations.Where(x => x.CommitSha == repo.Head).Select(x => new RevisionInformation { Id = x.Id }).FirstAsync(); compileJob = await instance.DreamMaker.Compile(projectName, timeout.Value, repo, cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 37ba9f674f..058016d906 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -205,5 +205,14 @@ namespace Tgstation.Server.Host.Controllers var instances = await DatabaseContext.Instances.Where(x => x.InstanceUsers.Any(y => y.UserId == AuthenticationContext.User.Id && y.AnyRights)).ToListAsync(cancellationToken).ConfigureAwait(false); return Json(instances); } + + /// + [TgsAuthorize] + public override Task Read(CancellationToken cancellationToken) + { + if (Instance == null) + return Task.FromResult(BadRequest(new { message = "No instance specified" })); + return Task.FromResult(Json(Instance.ToApi())); + } } } diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index f6aa72488c..1ea4c790a8 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -6,11 +6,6 @@ namespace Tgstation.Server.Host.Models /// public sealed class CompileJob : Api.Models.Internal.CompileJob, IApiConvertable { - /// - /// The of - /// - public long JobId { get; set; } - /// /// See /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 32fd9843ec..ad35a8baf9 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -109,7 +109,7 @@ namespace Tgstation.Server.Host.Models var revInfo = modelBuilder.Entity(); revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); revInfo.HasMany(x => x.TestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); - revInfo.HasIndex(x => x.Commit).IsUnique(); + revInfo.HasIndex(x => x.CommitSha).IsUnique(); var chatChannel = modelBuilder.Entity(); chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique(); diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 1a9d2d3360..69960d6356 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -29,8 +29,8 @@ namespace Tgstation.Server.Host.Models /// public Api.Models.RevisionInformation ToApi() => new Api.Models.RevisionInformation { - Commit = Commit, - OriginRevision = OriginRevision, + CommitSha = CommitSha, + OriginCommitSha = OriginCommitSha, TestMerges = TestMerges.Select(x => x.ToApi()).ToList() }; } From 3c17656ebc48c1cf6e2ff0768ef314a535936b48 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 10:48:30 -0400 Subject: [PATCH 340/397] Fix job manager controller not requiring instances --- src/Tgstation.Server.Host/Controllers/JobController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index c7bab7aee3..a8d2a20739 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory, false) + public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory, true) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); } From 55a17b37eaca43eb06ea92a29b528e6e91aa7faf Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 11:00:06 -0400 Subject: [PATCH 341/397] Prevent requests from running until the app is ready. Removes dead code --- .../Components/IInstanceShutdownHandler.cs | 22 ---------- .../Components/InstanceManager.cs | 41 +++++++++++-------- src/Tgstation.Server.Host/Core/Application.cs | 25 ++++++++++- .../Core/IApplication.cs | 6 +++ .../Tgstation.Server.Host.csproj | 1 + 5 files changed, 56 insertions(+), 39 deletions(-) delete mode 100644 src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs diff --git a/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs b/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs deleted file mode 100644 index 87264ca110..0000000000 --- a/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; - -namespace Tgstation.Server.Host.Components -{ - /// - /// For handling shutdowns - /// - public interface IInstanceShutdownHandler - { - //TODO - /// - /// OMG - /// - /// - /// - /// - /// - /// - Task PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary); - } -} diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 1d9671acff..c58e0e972b 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -25,14 +25,22 @@ namespace Tgstation.Server.Host.Components /// The for the /// readonly IInstanceFactory instanceFactory; + /// /// The for the /// readonly IIOManager ioManager; + /// /// The for the /// readonly IDatabaseContextFactory databaseContextFactory; + + /// + /// The for the + /// + readonly IApplication application; + /// /// Map of s to respective s /// @@ -48,11 +56,14 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory) + /// The value of + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); + instances = new Dictionary(); interopConsumers = new Dictionary(); } @@ -129,11 +140,19 @@ namespace Tgstation.Server.Host.Components /// public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext => { - await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); - var dbInstances = databaseContext.Instances.Where(x => x.Online.Value).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); - var tasks = new List(); - await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false); - await Task.WhenAll(tasks).ConfigureAwait(false); + try + { + await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); + var dbInstances = databaseContext.Instances.Where(x => x.Online.Value).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); + var tasks = new List(); + await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); + application.Ready(null); + } + catch (Exception e) + { + application.Ready(e); + } }); /// @@ -143,16 +162,6 @@ namespace Tgstation.Server.Host.Components instances.Clear(); } - /// - public Task PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary) - { - if (launchParameters == null) - throw new ArgumentNullException(nameof(launchParameters)); - if (accessToken == null) - throw new ArgumentNullException(nameof(accessToken)); - throw new NotImplementedException(); - } - /// public IInteropContext Register(string accessIdentifier, IInteropConsumer consumer) { diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b70dd81236..23cbaab64a 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,4 +1,5 @@ using Byond.TopicSender; +using Cyberboss.AspNetCore.AsyncInitializer; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -16,6 +17,7 @@ using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Watchdog; @@ -54,6 +56,8 @@ namespace Tgstation.Server.Host.Core /// readonly Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment; + readonly TaskCompletionSource startupTcs; + /// /// The for the /// @@ -69,6 +73,8 @@ namespace Tgstation.Server.Host.Core this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + startupTcs = new TaskCompletionSource(); + Version = Assembly.GetExecutingAssembly().GetName().Version; VersionString = String.Format(CultureInfo.InvariantCulture, "{0} v{1}", VersionPrefix, Version); } @@ -212,9 +218,26 @@ namespace Tgstation.Server.Host.Core if (hostingEnvironment.IsDevelopment()) applicationBuilder.UseDeveloperExceptionPage(); - + + applicationBuilder.UseAsyncInitialization(async cancellationToken => + { + using (cancellationToken.Register(() => startupTcs.SetCanceled())) + await startupTcs.Task.ConfigureAwait(false); + }); + applicationBuilder.UseAuthentication(); applicationBuilder.UseMvc(); } + + /// + public void Ready(Exception initializationError) + { + if (startupTcs.Task.IsCompleted) + throw new InvalidOperationException("Ready has already been called!"); + if (initializationError == null) + startupTcs.SetResult(null); + else + startupTcs.SetException(initializationError); + } } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/IApplication.cs b/src/Tgstation.Server.Host/Core/IApplication.cs index b00ae8a3b9..1cbe820b4c 100644 --- a/src/Tgstation.Server.Host/Core/IApplication.cs +++ b/src/Tgstation.Server.Host/Core/IApplication.cs @@ -21,5 +21,11 @@ namespace Tgstation.Server.Host.Core /// The url the server can be reached at locally /// string HostingPath { get; } + + /// + /// Mark the as ready to run + /// + /// The that put the application in a corrupted state if any + void Ready(Exception initializationError); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 53b174f006..5720992891 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -35,6 +35,7 @@ + From 7eaf6a633923275441d52707679c4cc25a3e9943 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 11:22:27 -0400 Subject: [PATCH 342/397] Add file logging --- .../Components/IInstanceManager.cs | 16 ++++++------- .../Configuration/GeneralConfiguration.cs | 23 +++++++++++++++++++ src/Tgstation.Server.Host/Core/Application.cs | 11 ++++++++- .../Models/DatabaseContext.cs | 10 -------- .../Tgstation.Server.Host.csproj | 2 +- src/Tgstation.Server.Host/appsettings.json | 4 ++++ 6 files changed, 46 insertions(+), 20 deletions(-) create mode 100644 src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index ca949b42a7..56b9333263 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -7,39 +7,39 @@ namespace Tgstation.Server.Host.Components /// /// For managing s /// - public interface IInstanceManager : IInstanceShutdownHandler + public interface IInstanceManager { /// /// Get the associated with given /// /// The of the desired /// The associated with the given - IInstance GetInstance(Host.Models.Instance metadata); + IInstance GetInstance(Models.Instance metadata); /// /// Online an /// - /// The of the desired + /// The of the desired /// The for the operation /// A representing the running operation - Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken); + Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken); /// /// Offline an /// - /// The of the desired + /// The of the desired /// The for the operation /// A representing the running operation - Task OfflineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken); + Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken); /// /// Move an /// - /// The of the desired + /// The of the desired /// The new path of the . will have this set on if the operation completes successfully /// The for the operation /// A representing the running operation - Task MoveInstance(Host.Models.Instance metadata, string newPath, CancellationToken cancellationToken); + Task MoveInstance(Models.Instance metadata, string newPath, CancellationToken cancellationToken); /// /// Handle a GET via world/Export diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs new file mode 100644 index 0000000000..453fd72ba8 --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -0,0 +1,23 @@ +namespace Tgstation.Server.Host.Configuration +{ + /// + /// General configuration options + /// + sealed class GeneralConfiguration + { + /// + /// The key for the the resides in + /// + public const string Section = "General"; + + /// + /// Where log files are stored + /// + public string LogFilePath { get; set; } + + /// + /// If file logging is disabled + /// + public bool DisableFileLogging { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 23cbaab64a..3bba421036 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -9,6 +9,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Octokit; using System; @@ -93,9 +95,16 @@ namespace Tgstation.Server.Host.Core services.Configure(configuration.GetSection(UpdatesConfiguration.Section)); var databaseConfigurationSection = configuration.GetSection(DatabaseConfiguration.Section); services.Configure(databaseConfigurationSection); + var generalConfigurationSection = configuration.GetSection(GeneralConfiguration.Section); + services.Configure(generalConfigurationSection); + + var generalConfiguration = generalConfigurationSection.Get(); + + if (!generalConfiguration.DisableFileLogging) + services.AddLogging(builder => builder.AddFile(generalConfiguration.LogFilePath)); services.AddOptions(); - + const string scheme = "JwtBearer"; services.AddAuthentication((options) => { diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index ad35a8baf9..5c52b6d5f6 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; -using ZNetCS.AspNetCore.Logging.EntityFrameworkCore; namespace Tgstation.Server.Host.Models { @@ -38,11 +37,6 @@ namespace Tgstation.Server.Host.Models /// public DbSet RepositorySettings { get; set; } - /// - /// The for s - /// - public DbSet Logs { get; set; } - /// /// The s in the /// @@ -96,10 +90,6 @@ namespace Tgstation.Server.Host.Models { base.OnModelCreating(modelBuilder); - // build default model. - LogModelBuilderHelper.Build(modelBuilder.Entity()); - modelBuilder.Entity().ToTable(nameof(Logs)); - var userModel = modelBuilder.Entity(); userModel.HasIndex(x => x.CanonicalName).IsUnique(); userModel.HasMany(x => x.TestMerges).WithOne(x => x.MergedBy).OnDelete(DeleteBehavior.Restrict); diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 5720992891..f7ba838484 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -50,11 +50,11 @@ + - diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 3ea279ea3c..1b10e9595f 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -1,4 +1,8 @@ { + "General": { + "LogFilePath": null, //use the default path + "DisableFileLogging": false + }, "Logging": { "IncludeScopes": false, "Debug": { From 99f650ba912b5a34ecf0aab09a48fc0884b56125 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 11:47:09 -0400 Subject: [PATCH 343/397] Adds default log file path. Other file log fixups --- .../Configuration/GeneralConfiguration.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 15 +++++++++------ src/Tgstation.Server.Host/appsettings.json | 16 ++++++++-------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index 453fd72ba8..f789eb4787 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -13,7 +13,7 @@ /// /// Where log files are stored /// - public string LogFilePath { get; set; } + public string LogFileDirectory { get; set; } /// /// If file logging is disabled diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 3bba421036..fae43df35a 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Octokit; using System; @@ -99,9 +98,14 @@ namespace Tgstation.Server.Host.Core services.Configure(generalConfigurationSection); var generalConfiguration = generalConfigurationSection.Get(); + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var ioManager = new DefaultIOManager(); if (!generalConfiguration.DisableFileLogging) - services.AddLogging(builder => builder.AddFile(generalConfiguration.LogFilePath)); + { + var logPath = !String.IsNullOrEmpty(generalConfiguration.LogFileDirectory) ? generalConfiguration.LogFileDirectory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs"); + services.AddLogging(builder => builder.AddFile(ioManager.ConcatPath(logPath, "tgs-{Date}.log"))); + } services.AddOptions(); @@ -174,7 +178,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(x => new GitHubClient(new ProductHeaderValue(VersionPrefix, Version.ToString()))); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (isWindows) { services.AddSingleton(); services.AddSingleton(); @@ -204,9 +208,8 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); - - services.AddSingleton(); - services.AddSingleton(x => x.GetRequiredService()); + + services.AddSingleton(ioManager); services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 1b10e9595f..55cb8d5cce 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -1,25 +1,25 @@ { "General": { - "LogFilePath": null, //use the default path + "LogFileDirectory": null, //use the default path "DisableFileLogging": false }, "Logging": { "IncludeScopes": false, "Debug": { "LogLevel": { - "Default": "Debug" + "Default": "Debug", + "Microsoft": "Information" } }, "Console": { "LogLevel": { - "Microsoft.EntityFrameworkCore": "Warning", - "Default": "Trace" + "Default": "Trace", + "Microsoft": "Information" } }, - "EntityFramework": { - "LogLevel": { - "Default": "Information" - } + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning" } }, "Updates": { From 7e889f41dceb1cec0bde45d0f41d495c49873499 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 11:49:34 -0400 Subject: [PATCH 344/397] Adds file logging stuff to the docker config --- build/Dockerfile | 2 +- src/Tgstation.Server.Host/appsettings.Docker.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 277e79bb8e..9d8837adc2 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -48,6 +48,6 @@ COPY --from=build /app . COPY --from=build /src/build/tgs.docker.sh tgs.sh RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json -VOLUME ["/config_data"] +VOLUME ["/config_data", "/tgs_logs"] ENTRYPOINT ["./tgs.sh"] diff --git a/src/Tgstation.Server.Host/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json index 906e461ee4..233889ca3f 100644 --- a/src/Tgstation.Server.Host/appsettings.Docker.json +++ b/src/Tgstation.Server.Host/appsettings.Docker.json @@ -1,4 +1,7 @@ { + "General": { + "LogFileDirectory": "/tgs_logs" + }, "Database": { "DatabaseType": "SqlServer", "ConnectionString": "" From 597b2f0b290b81bb733ab174a7649b6ee11e4498 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 11:54:32 -0400 Subject: [PATCH 345/397] Change the docker port to 5000 --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 9d8837adc2..9af459a12b 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -40,7 +40,7 @@ WORKDIR /src/src/Tgstation.Server.Host RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app FROM microsoft/dotnet:2.1-aspnetcore-runtime -EXPOSE 80 +EXPOSE 5000 WORKDIR /app From ba511e1826fe6c275f8ff668266dccf1a70f4a93 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 11:57:26 -0400 Subject: [PATCH 346/397] No wait, docker ALWAYS uses 80 --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 9af459a12b..9d8837adc2 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -40,7 +40,7 @@ WORKDIR /src/src/Tgstation.Server.Host RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app FROM microsoft/dotnet:2.1-aspnetcore-runtime -EXPOSE 5000 +EXPOSE 80 WORKDIR /app From 4ebd28f7b75a781808b616099609328544bd2059 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 13:35:07 -0400 Subject: [PATCH 347/397] Successful instance startup and shutdown --- .../Models/Internal/RepositorySettings.cs | 6 +- .../Components/ByondManager.cs | 26 +++- .../Components/Chat/Chat.cs | 23 ++- .../Components/Chat/ChatFactory.cs | 3 +- .../Components/Chat/IChatFactory.cs | 7 +- .../Components/DmbFactory.cs | 15 +- .../Components/IInstance.cs | 3 +- .../Components/Instance.cs | 8 +- .../Components/InstanceFactory.cs | 22 ++- .../Components/InstanceManager.cs | 49 +++++-- .../Components/ReattachInfoHandler.cs | 8 +- .../Watchdog/SessionControllerFactory.cs | 6 +- .../Components/Watchdog/Watchdog.cs | 19 ++- .../Components/Watchdog/WatchdogFactory.cs | 6 +- .../Controllers/InstanceController.cs | 90 ++++++++++-- tools/TGS.postman_collection.json | 137 +++++++++++++++++- 16 files changed, 341 insertions(+), 87 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index 5893293fa7..c5ace7499c 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -39,12 +39,14 @@ namespace Tgstation.Server.Api.Models.Internal /// If commits created from testmerges are pushed to the remote /// [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] - public bool PushTestMergeCommits { get; set; } + [Required] + public bool? PushTestMergeCommits { get; set; } /// /// If test merge commits are signed with the username of the person who merged it. Note this only affects future commits /// [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] - public bool ShowTestMergeCommitters { get; set; } + [Required] + public bool? ShowTestMergeCommitters { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs index 4b85832126..70af749930 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -125,8 +125,16 @@ namespace Tgstation.Server.Host.Components { var cacheCleanTask = byondInstaller.CleanCache(cancellationToken); - var activeVersionBytesTask = ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken); + async Task GetActiveVersion() + { + if (!await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false)) + return null; + return await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); + } + var activeVersionBytesTask = GetActiveVersion(); + + await ioManager.CreateDirectory(".", cancellationToken).ConfigureAwait(false); var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false); async Task ReadVersion(string path) @@ -148,13 +156,17 @@ namespace Tgstation.Server.Host.Components await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false); - var activeVersionString = Encoding.UTF8.GetString(await activeVersionBytesTask.ConfigureAwait(false)); - if (Version.TryParse(activeVersionString, out var activeVersion)) - ActiveVersion = activeVersion; - else - await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); + var activeVersionBytes = await activeVersionBytesTask.ConfigureAwait(false); + if (activeVersionBytes != null) + { + var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes); + if (Version.TryParse(activeVersionString, out var activeVersion)) + ActiveVersion = activeVersion; + else + await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); - await cacheCleanTask.ConfigureAwait(false); + await cacheCleanTask.ConfigureAwait(false); + } } /// diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index e878aedaa3..1da84cd9fe 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -57,6 +57,11 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly CancellationTokenSource handlerCts; + /// + /// The initial for the + /// + readonly List initialChatSettings; + /// /// The for the /// @@ -84,14 +89,17 @@ namespace Tgstation.Server.Host.Components.Chat /// The value of /// The value of /// The used to populate - public Chat(IProviderFactory providerFactory, IIOManager ioManager, ILogger logger, ICommandFactory commandFactory) + /// The used to populate + public Chat(IProviderFactory providerFactory, IIOManager ioManager, ILogger logger, ICommandFactory commandFactory, IEnumerable initialChatSettings) { this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.initialChatSettings = initialChatSettings?.ToList() ?? throw new ArgumentNullException(nameof(initialChatSettings)); + builtinCommands = new Dictionary(); foreach (var I in commandFactory?.GenerateCommands() ?? throw new ArgumentNullException(nameof(commandFactory))) - builtinCommands.Add(I.Name, I); + builtinCommands.Add(I.Name.ToUpperInvariant(), I); providers = new Dictionary(); mappedChannels = new Dictionary(); @@ -221,9 +229,14 @@ namespace Tgstation.Server.Host.Components.Chat if (I.Value.Connected && !messageTasks.ContainsKey(I.Value)) messageTasks.Add(I.Value, I.Value.NextMessage(cancellationToken)); + if(messageTasks.Count == 0) + { + await Task.Delay(1000, cancellationToken).ConfigureAwait(false); + continue; + } + //wait for a message - var tasks = messageTasks.Select(x => x.Value); - await Task.WhenAny().ConfigureAwait(false); + await Task.WhenAny(messageTasks.Select(x => x.Value)).ConfigureAwait(false); //process completed ones foreach (var I in messageTasks.Where(x => x.Value.IsCompleted)) @@ -345,7 +358,9 @@ namespace Tgstation.Server.Host.Components.Chat /// public async Task StartAsync(CancellationToken cancellationToken) { + await Task.WhenAll(initialChatSettings.Select(x => ChangeSettings(x, cancellationToken))).ConfigureAwait(false); await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))).ConfigureAwait(false); + await Task.WhenAll(initialChatSettings.Select(x => ChangeChannels(x.Id, x.Channels, cancellationToken))).ConfigureAwait(false); chatHandler = MonitorMessages(handlerCts.Token); started = true; } diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs index 339664001e..14a6370e54 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.IO; @@ -37,6 +38,6 @@ namespace Tgstation.Server.Host.Components.Chat } /// - public IChat CreateChat() => new Chat(new ProviderFactory(), ioManager, loggerFactory.CreateLogger(), commandFactory); + public IChat CreateChat(IEnumerable initialChatSettings) => new Chat(new ProviderFactory(), ioManager, loggerFactory.CreateLogger(), commandFactory, initialChatSettings); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs index 734b6243f3..44802b484f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatFactory.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Host.Components.Chat +using System.Collections.Generic; + +namespace Tgstation.Server.Host.Components.Chat { /// /// For creating s @@ -8,7 +10,8 @@ /// /// Create a /// + /// The initial for the /// A new - IChat CreateChat(); + IChat CreateChat(IEnumerable initialChatSettings); } } diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index 28ace4fa08..0cb7fb2a2d 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -39,9 +39,9 @@ namespace Tgstation.Server.Host.Components readonly CancellationTokenSource cleanupCts; /// - /// The the belongs to + /// The for the /// - readonly long instanceId; + readonly Api.Models.Instance instance; /// /// representing calls to @@ -63,13 +63,14 @@ namespace Tgstation.Server.Host.Components /// /// The value of /// The value of - /// The used to populate - public DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager, Models.Instance instance) + /// The value of + public DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager, Api.Models.Instance instance) { this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); - instanceId = instance?.Id ?? throw new ArgumentNullException(nameof(instance)); + this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); + cleanupTask = Task.CompletedTask; cleanupCts = new CancellationTokenSource(); jobLockCounts = new Dictionary(); } @@ -113,7 +114,7 @@ namespace Tgstation.Server.Host.Components { var ddsettings = new DreamDaemonSettings { - InstanceId = instanceId + InstanceId = instance.Id }; db.DreamDaemonSettings.Attach(ddsettings); ddsettings.StagedCompileJob = job; @@ -152,7 +153,7 @@ namespace Tgstation.Server.Host.Components public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { //where complete clause not necessary, only successful COMPILEjobs get in the db - var cj = await db.Instances.Where(x => x.Id == instanceId).SelectMany(x => x.RevisionInformations).SelectMany(x => x.CompileJobs).OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var cj = await db.Instances.Where(x => x.Id == instance.Id).SelectMany(x => x.RevisionInformations).SelectMany(x => x.CompileJobs).OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (cj == default(CompileJob)) return; var directoriesTask = ioManager.GetDirectories(".", cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index ffcf471681..f5d3db11dc 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Hosting; +using System; using System.Threading.Tasks; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Watchdog; @@ -8,7 +9,7 @@ namespace Tgstation.Server.Host.Components /// /// For interacting with the instance services /// - public interface IInstance : IHostedService + public interface IInstance : IHostedService, IDisposable { /// /// The for the diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 4c58438157..6ee66aceb6 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -10,7 +10,7 @@ using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { /// - sealed class Instance : IInstance, IDisposable + sealed class Instance : IInstance { /// public IRepositoryManager RepositoryManager { get; } @@ -65,7 +65,7 @@ namespace Tgstation.Server.Host.Components RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); ByondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager)); DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)); - watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); + Watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); @@ -132,10 +132,10 @@ namespace Tgstation.Server.Host.Components } /// - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken)); + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)); /// - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken)); + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); /// public async Task SetAutoUpdateInterval(int? newInterval) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 6e67f0ff26..9afa78490a 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -116,7 +116,7 @@ namespace Tgstation.Server.Host.Components var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); - var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, metadata); + var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, metadata.CloneMetadata()); try { var commandFactory = new CommandFactory(application); @@ -128,18 +128,28 @@ namespace Tgstation.Server.Host.Components var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger()); var configuration = new Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); - var chat = chatFactory.CreateChat(); + var chat = chatFactory.CreateChat(metadata.ChatSettings); try { - var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata); - var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata); - var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, metadata); + + foreach (var I in metadata.ChatSettings) + { + //required to be synchrounous + var task = chat.ChangeSettings(I, default); + if (!task.IsCompleted) + throw new InvalidOperationException("Initial chat setup is asynchronous!"); + task.GetAwaiter().GetResult(); + } + + var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata()); + var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata.CloneMetadata()); + var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, metadata.CloneMetadata()); var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings); try { var dreamMaker = new DreamMaker(byond, ioManager, configuration, sessionControllerFactory, dmbFactory, application, watchdog, loggerFactory.CreateLogger()); - return new Instance(metadata, repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory); + return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory); } catch { diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index c58e0e972b..913d4af279 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -7,14 +7,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components { /// - sealed class InstanceManager : IInstanceManager, IHostedService, IInteropRegistrar + sealed class InstanceManager : IInstanceManager, IHostedService, IInteropRegistrar, IDisposable { /// /// HTTP GET query key for interop access identifiers @@ -68,6 +67,13 @@ namespace Tgstation.Server.Host.Components interopConsumers = new Dictionary(); } + /// + public void Dispose() + { + foreach (var I in instances) + I.Value.Dispose(); + } + /// public IInstance GetInstance(Models.Instance metadata) { @@ -119,7 +125,14 @@ namespace Tgstation.Server.Host.Components throw new InvalidOperationException("Instance not online!"); instances.Remove(metadata.Id); } - await instance.StopAsync(cancellationToken).ConfigureAwait(false); + try + { + await instance.StopAsync(cancellationToken).ConfigureAwait(false); + } + finally + { + instance.Dispose(); + } } /// @@ -128,11 +141,19 @@ namespace Tgstation.Server.Host.Components if (metadata == null) throw new ArgumentNullException(nameof(metadata)); var instance = instanceFactory.CreateInstance(metadata, this); - lock (this) + try { - if (instances.ContainsKey(metadata.Id)) - throw new InvalidOperationException("Instance already online!"); - instances.Add(metadata.Id, instance); + lock (this) + { + if (instances.ContainsKey(metadata.Id)) + throw new InvalidOperationException("Instance already online!"); + instances.Add(metadata.Id, instance); + } + } + catch + { + instance.Dispose(); + throw; } await instance.StartAsync(cancellationToken).ConfigureAwait(false); } @@ -143,9 +164,13 @@ namespace Tgstation.Server.Host.Components try { await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); - var dbInstances = databaseContext.Instances.Where(x => x.Online.Value).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); + var dbInstances = databaseContext.Instances.Where(x => x.Online.Value) + .Include(x => x.RepositorySettings) + .Include(x => x.ChatSettings) + .Include(x => x.DreamDaemonSettings) + .ToAsyncEnumerable(); var tasks = new List(); - await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false); + await dbInstances.ForEachAsync(metadata => tasks.Add(metadata.Online.Value ? OnlineInstance(metadata, cancellationToken) : Task.CompletedTask), cancellationToken).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false); application.Ready(null); } @@ -156,11 +181,7 @@ namespace Tgstation.Server.Host.Components }); /// - public async Task StopAsync(CancellationToken cancellationToken) - { - await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); - instances.Clear(); - } + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))); /// public IInteropContext Register(string accessIdentifier, IInteropConsumer consumer) diff --git a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs index 93c7e6aa91..b72af7cf8e 100644 --- a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs +++ b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs @@ -22,9 +22,9 @@ namespace Tgstation.Server.Host.Components readonly IDmbFactory dmbFactory; /// - /// The for the + /// The for the /// - readonly Models.Instance metadata; + readonly Api.Models.Instance metadata; /// /// Construct a @@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public ReattachInfoHandler(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, Models.Instance metadata) + public ReattachInfoHandler(IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, Api.Models.Instance metadata) { this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); @@ -47,6 +47,8 @@ namespace Tgstation.Server.Host.Components Models.ReattachInformation ConvertReattachInfo(ReattachInformation wdInfo) { + if (wdInfo == null) + return null; db.CompileJobs.Attach(wdInfo.Dmb.CompileJob); return new Models.ReattachInformation { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 4b29138ef0..ec2afa72a0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -64,9 +64,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly ILoggerFactory loggerFactory; /// - /// The for the + /// The for the /// - readonly Models.Instance instance; + readonly Api.Models.Instance instance; /// /// Construct a @@ -81,7 +81,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public SessionControllerFactory(IExecutor executor, IByondManager byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Models.Instance instance) + public SessionControllerFactory(IExecutor executor, IByondManager byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Api.Models.Instance instance) { this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index feedd64b35..25e85688e0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -81,9 +81,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly SemaphoreSlim semaphore; /// - /// The the belongs to + /// The for the /// - readonly long instanceId; + readonly Api.Models.Instance instance; /// /// If the should in @@ -131,9 +131,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The initial value of - /// The containing the value of + /// The value of /// The value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, DreamDaemonLaunchParameters initialLaunchParameters, Models.Instance instance, bool autoStart) + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -142,7 +142,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); - instanceId = instance?.Id ?? throw new ArgumentNullException(nameof(instance)); + this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.autoStart = autoStart; if (serverUpdater == null) @@ -678,7 +678,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { var settings = new Models.DreamDaemonSettings { - InstanceId = instanceId + InstanceId = instance.Id }; var cj = (AlphaIsActive ? alphaServer : bravoServer).Dmb.CompileJob; db.CompileJobs.Attach(cj); @@ -760,9 +760,12 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task StopAsync(CancellationToken cancellationToken) { - if (releaseServers) + if (releaseServers && Running) { - var reattachInformation = new WatchdogReattachInformation { AlphaIsActive = AlphaIsActive }; + var reattachInformation = new WatchdogReattachInformation + { + AlphaIsActive = AlphaIsActive + }; reattachInformation.Alpha = alphaServer?.Release(); reattachInformation.Bravo = bravoServer?.Release(); await reattachInfoHandler.Save(reattachInformation, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 6c948960d0..a3dca7bb30 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -46,9 +46,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly IByondTopicSender byondTopicSender; /// - /// The for the + /// The for the /// - readonly Models.Instance instance; + readonly Api.Models.Instance instance; /// @@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, Models.Instance instance) + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, Api.Models.Instance instance) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 058016d906..23ebad9983 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Linq.Expressions; @@ -37,6 +38,16 @@ namespace Tgstation.Server.Host.Controllers /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly IApplication application; + + /// + /// The for the + /// + readonly ILogger logger; + /// /// Construct a /// @@ -45,25 +56,37 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager) : base(databaseContext, authenticationContextFactory, false) + /// The value of + /// The value of + public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager, IApplication application, ILogger logger) : base(databaseContext, authenticationContextFactory, false) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + void NormalizeModelPath(Api.Models.Instance model) + { + if (model.Path == null) + return; + model.Path = ioManager.ResolvePath(model.Path); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + model.Path = model.Path.ToUpperInvariant(); } /// [TgsAuthorize(InstanceManagerRights.Create)] public override async Task Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) { - if (String.IsNullOrWhiteSpace(model.Name)) return BadRequest(new { message = "name must not be empty!" }); if(model.Path == null) return BadRequest(new { message = "path must not be empty!" }); - model.Path = ioManager.ResolvePath(model.Path); + NormalizeModelPath(model); var dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken); if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false)) return Conflict(new { message = "Path not empty!" }); @@ -71,12 +94,28 @@ namespace Tgstation.Server.Host.Controllers var newInstance = new Models.Instance { ConfigurationType = model.ConfigurationType ?? ConfigurationType.Disallowed, - DreamDaemonSettings = new DreamDaemonSettings(), + DreamDaemonSettings = new DreamDaemonSettings + { + AllowWebClient = false, + AutoStart = false, + PrimaryPort = 1337, + SecondaryPort = 1338, + SecurityLevel = DreamDaemonSecurity.Ultrasafe, + SoftRestart = false, + SoftShutdown = false, + StartupTimeout = 20 + }, DreamMakerSettings = new DreamMakerSettings(), Name = model.Name, Online = false, - Path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? model.Path.ToUpperInvariant() : model.Path, - RepositorySettings = new RepositorySettings() + Path = model.Path, + RepositorySettings = new RepositorySettings + { + CommitterEmail = "tgstation-server@user.noreply.github.com", + CommitterName = application.VersionString, + PushTestMergeCommits = false, + ShowTestMergeCommitters = true, + } }; DatabaseContext.Instances.Add(newInstance); @@ -103,6 +142,8 @@ namespace Tgstation.Server.Host.Controllers { return Conflict(new { message = e.Message }); } + + logger.LogInformation("{0} created instance {1}: {2}", AuthenticationContext.User.Name, newInstance.Name, newInstance.Id); return Json(newInstance.ToApi()); } @@ -137,7 +178,11 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(InstanceManagerRights.Relocate | InstanceManagerRights.Rename | InstanceManagerRights.SetAutoUpdate | InstanceManagerRights.SetConfiguration | InstanceManagerRights.SetOnline)] public override async Task Update([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) { - var originalModel = await DatabaseContext.Instances.Where(x => x.Id == model.Id).FirstAsync(cancellationToken).ConfigureAwait(false); + var originalModel = await DatabaseContext.Instances.Where(x => x.Id == model.Id) + .Include(x => x.RepositorySettings) + .Include(x => x.ChatSettings) + .Include(x => x.DreamDaemonSettings) //need these for onlining + .FirstAsync(cancellationToken).ConfigureAwait(false); if (originalModel == default(Models.Instance)) return StatusCode((int)HttpStatusCode.Gone); @@ -160,8 +205,7 @@ namespace Tgstation.Server.Host.Controllers string originalModelPath = null; if (model.Path != null) { - model.Path = ioManager.ResolvePath(model.Path); - model.Path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? model.Path.ToUpperInvariant() : model.Path; + NormalizeModelPath(model); if (model.Path != originalModel.Path) { @@ -179,6 +223,8 @@ namespace Tgstation.Server.Host.Controllers } } + var originalOnline = originalModel.Online.Value; + if (CheckModified(x => x.AutoUpdateInterval, InstanceManagerRights.SetAutoUpdate) || CheckModified(x => x.ConfigurationType, InstanceManagerRights.SetConfiguration) || CheckModified(x => x.Name, InstanceManagerRights.Rename) @@ -187,10 +233,21 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - if (originalModel.Online.Value && model.Online.Value == false) - await instanceManager.OfflineInstance(originalModel, cancellationToken).ConfigureAwait(false); - else if(!originalModel.Online.Value && model.Online.Value == true) - await instanceManager.OnlineInstance(originalModel, cancellationToken).ConfigureAwait(false); + try + { + if (originalOnline && model.Online.Value == false) + await instanceManager.OfflineInstance(originalModel, cancellationToken).ConfigureAwait(false); + else if (!originalOnline && model.Online.Value == true) + await instanceManager.OnlineInstance(originalModel, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogError("Error changing instance online state! Exception: {0}", e); + originalModel.Online = originalOnline; + originalModel.Path = originalModelPath; + await DatabaseContext.Save(default).ConfigureAwait(false); + throw; + } if (originalModelPath != null) await ioManager.MoveDirectory(originalModelPath, model.Path, cancellationToken).ConfigureAwait(false); @@ -202,8 +259,11 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize] public override async Task List(CancellationToken cancellationToken) { - var instances = await DatabaseContext.Instances.Where(x => x.InstanceUsers.Any(y => y.UserId == AuthenticationContext.User.Id && y.AnyRights)).ToListAsync(cancellationToken).ConfigureAwait(false); - return Json(instances); + IQueryable query = DatabaseContext.Instances; + if (!AuthenticationContext.User.InstanceManagerRights.Value.HasFlag(InstanceManagerRights.List)) + query = query.Where(x => x.InstanceUsers.Any(y => y.UserId == AuthenticationContext.User.Id)).Where(x => x.InstanceUsers.Any(y => y.AnyRights)); + var instances = await query.ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(instances.Select(x => x.ToApi())); } /// diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index 22d1a9d5b8..2f26a89582 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "e62d1230-5b76-45e2-9fb6-002e260daa2a", + "_postman_id": "a318afb1-1fa5-4d13-b007-9fb6ab4930e0", "name": "TGS", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -144,7 +144,7 @@ }, { "name": "Users", - "description": "", + "description": null, "item": [ { "name": "Read Current User", @@ -164,7 +164,10 @@ "value": "Tgstation.Server.Api/4.0.0.0" } ], - "body": {}, + "body": { + "mode": "raw", + "raw": "" + }, "url": { "raw": "localhost:5000/User", "host": [ @@ -196,7 +199,10 @@ "value": "Tgstation.Server.Api/4.0.0.0" } ], - "body": {}, + "body": { + "mode": "raw", + "raw": "" + }, "url": { "raw": "localhost:5000/User/List", "host": [ @@ -613,10 +619,127 @@ }, { "name": "Instance", - "description": "", + "description": null, "item": [ { - "name": "Create Instance TestInstance", + "name": "Online Instance ID 1", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": true\n}" + }, + "url": { + "raw": "localhost:5000/Instance", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Instance" + ] + } + }, + "response": [] + }, + { + "name": "Offline Instance ID 1", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Instance", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Instance" + ] + } + }, + "response": [] + }, + { + "name": "List instances", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Instance", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Instance" + ] + } + }, + "response": [] + }, + { + "name": "Create Instance TestInstance Copy", "request": { "method": "PUT", "header": [ @@ -782,7 +905,7 @@ "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjY1ODQ4MiIsIm5iZiI6IjE1MzI2NTQ4ODIiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.GgcD2qfa5bA16w-lb2oEZLcieZhSGWg9wIv-OvhqGuA", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjcxNjIzMSIsIm5iZiI6IjE1MzI3MTI2MzEiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.Kyuh1sX51BecGNcD8WiL_mXpgSTwkMV2EmVwoLBoxWo", "type": "string" } ] From 9c504601cfd48a51be1b722668ed0c3729c96852 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 14:26:26 -0400 Subject: [PATCH 348/397] Fixes oof --- appveyor.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 15541bc685..539f2fb6d2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -62,10 +62,9 @@ after_test: - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse - ps: Remove-Item artifacts/ServerHost/appsettings.Docker.json - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerService/lib/Default -recurse - - ps: ls artifacts/ServerService #I have literally no idea how these jsons got here - ps: Remove-Item -path artifacts/ServerService/appsettings.json - - ps: Remove-Item -path artifacts/ServerService/appsettings..Docker.json + - ps: Remove-Item -path artifacts/ServerService/appsettings.Docker.json - ps: Move-Item -path artifacts/ServerService/lib/Default/appsettings.json -destination artifacts/ServerService/ - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerConsole/lib/Default -recurse - ps: Move-Item -path artifacts/ServerConsole/lib/Default/appsettings.json -destination artifacts/ServerConsole/ From f54a807dfc39775b8955360b0f865ba835889587 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 27 Jul 2018 17:05:00 -0400 Subject: [PATCH 349/397] Pushing through on prototyping --- .../Models/ChatChannel.cs | 10 +- .../Models/InstanceUser.cs | 8 +- .../Components/Chat/Chat.cs | 4 +- .../Components/Chat/ChatFactory.cs | 11 +- .../Components/Chat/ProviderFactory.cs | 70 ++++- .../Chat/Providers/DiscordProvider.cs | 2 +- .../Components/Chat/Providers/IrcProvider.cs | 2 +- .../Components/InstanceFactory.cs | 21 +- .../Components/InstanceManager.cs | 1 + .../Controllers/ApiController.cs | 1 - .../Controllers/ChatController.cs | 15 +- .../Controllers/InstanceController.cs | 32 +- .../Controllers/InstanceUserController.cs | 18 +- src/Tgstation.Server.Host/Core/Application.cs | 2 + .../Models/ChatChannel.cs | 15 +- .../Models/ChatSettings.cs | 15 +- .../Models/InstanceUser.cs | 4 +- .../Security/AuthenticationContextFactory.cs | 17 +- tools/TGS.postman_collection.json | 283 +++++++++++++++++- v4_prototype_TODO.txt | 4 + 20 files changed, 474 insertions(+), 61 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index d5131f4ff1..946613c1b2 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Api.Models +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models { /// /// Indicates a chat channel @@ -18,11 +20,13 @@ /// /// If the is an admin channel /// - public bool IsAdminChannel { get; set; } + [Required] + public bool? IsAdminChannel { get; set; } /// /// If the is a watchdog channel /// - public bool IsWatchdogChannel { get; set; } + [Required] + public bool? IsWatchdogChannel { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 58ed24033c..2732ef66c3 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// /// Represents a s permissions in an /// - [Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)] + [Model(RightsType.InstanceUser, WriteRight = Rights.InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)] public class InstanceUser { /// @@ -23,6 +23,12 @@ namespace Tgstation.Server.Api.Models [Required] public long? InstanceId { get; set; } + /// + /// The of the + /// + [Required] + public InstanceUserRights? InstanceUserRights { get; set; } + /// /// The of the /// diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 1da84cd9fe..be8fed89fd 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -156,7 +156,7 @@ namespace Tgstation.Server.Host.Components.Chat { logger.LogTrace("Chat message: {0}. User (Note unconverted provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User)); - var splits = new List(message.Content.Split(' ')); + var splits = new List(message.Content.TrimEnd().Split(' ')); var address = splits[0]; if (address.Length > 1 && (address[address.Length - 1] == ':' || address[address.Length - 1] == ',')) address = address.Substring(0, address.Length - 1); @@ -265,7 +265,7 @@ namespace Tgstation.Server.Host.Components.Chat return; var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping { - IsWatchdogChannel = x.IsWatchdogChannel, + IsWatchdogChannel = x.IsWatchdogChannel == true, ProviderChannelId = y.RealId, ProviderId = connectionId, Channel = y diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs index 14a6370e54..a5624d7f20 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs @@ -24,20 +24,27 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly ICommandFactory commandFactory; + /// + /// The for the + /// + readonly IProviderFactory providerFactory; + /// /// Construct a /// /// The value of /// The value of /// The value of - public ChatFactory(IIOManager ioManager, ILoggerFactory loggerFactory, ICommandFactory commandFactory) + /// The value of + public ChatFactory(IIOManager ioManager, ILoggerFactory loggerFactory, ICommandFactory commandFactory, IProviderFactory providerFactory) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); + this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); } /// - public IChat CreateChat(IEnumerable initialChatSettings) => new Chat(new ProviderFactory(), ioManager, loggerFactory.CreateLogger(), commandFactory, initialChatSettings); + public IChat CreateChat(IEnumerable initialChatSettings) => new Chat(providerFactory, ioManager, loggerFactory.CreateLogger(), commandFactory, initialChatSettings); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs index b16cd3617c..bb3b6fd20f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs @@ -1,13 +1,37 @@ -using System; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; using System.Globalization; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Chat.Providers; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Chat { /// sealed class ProviderFactory : IProviderFactory { + /// + /// The for the + /// + readonly ILoggerFactory loggerFactory; + + /// + /// The for the + /// + readonly IApplication application; + + /// + /// Construct a + /// + /// The value of + /// The value of + public ProviderFactory(ILoggerFactory loggerFactory, IApplication application) + { + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + this.application = application ?? throw new ArgumentNullException(nameof(application)); + } + /// public IProvider CreateProvider(Api.Models.Internal.ChatSettings settings) { @@ -16,9 +40,49 @@ namespace Tgstation.Server.Host.Components.Chat switch (settings.Provider) { case ChatProvider.Irc: - throw new NotImplementedException(); + //Connection string semicolon delimited until the password field + if (settings.ConnectionString == null) + throw new InvalidOperationException("ConnectionString cannot be null!"); + var splits = settings.ConnectionString.Split(';'); + if (splits.Length < 4) + throw new InvalidOperationException("Invalid connection string!"); + + var address = splits[0]; + if (!UInt16.TryParse(splits[1], out var port)) + throw new InvalidOperationException("Unable to parse port!"); + var nick = splits[2]; + if (!Int32.TryParse(splits[3], out var intSsl)) + throw new InvalidOperationException("Unable to parse ssl option!"); + + IrcPasswordType? passwordType = null; + string password = null; + if(splits.Length > 4) + { + if (splits.Length < 6) + throw new InvalidOperationException("Invalid connection string!"); + if (!Int32.TryParse(splits[4], out var intPasswordType)) + throw new InvalidOperationException("Unable to parse password type!"); + + passwordType = (IrcPasswordType)intPasswordType; + switch (passwordType) + { + case IrcPasswordType.NickServ: + case IrcPasswordType.Sasl: + case IrcPasswordType.Server: + break; + default: + throw new InvalidOperationException("Invalid password type!"); + } + + var rest = new List(splits); + rest.RemoveRange(0, 5); + password = String.Join(";", rest); + } + + return new IrcProvider(loggerFactory.CreateLogger(), application, address, port, nick, password, passwordType, intSsl != 0); case ChatProvider.Discord: - throw new NotImplementedException(); + //discord is just the bot token + return new DiscordProvider(loggerFactory.CreateLogger(), settings.ConnectionString); default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid ChatProvider: {0}", settings.Provider)); } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 4412cf16b5..7b4694d574 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -171,7 +171,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return new Channel { RealId = discordChannel.Id, - IsAdmin = channel.IsAdminChannel, + IsAdmin = channel.IsAdminChannel == true, ConnectionName = discordChannel.Guild.Name, FriendlyName = discordChannel.Name, IsPrivate = false diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 44966939e3..557b980fd8 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -329,7 +329,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return new Channel { RealId = id, - IsAdmin = x.IsAdminChannel, + IsAdmin = x.IsAdminChannel == true, ConnectionName = address, FriendlyName = channelIdMap[id], IsPrivate = false diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 9afa78490a..527fdad14c 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -73,6 +73,11 @@ namespace Tgstation.Server.Host.Components /// readonly IByondInstaller byondInstaller; + /// + /// The for the + /// + readonly IProviderFactory providerFactory; + /// /// Construct an /// @@ -88,7 +93,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -102,6 +108,7 @@ namespace Tgstation.Server.Host.Components this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller)); + this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); } /// @@ -120,7 +127,7 @@ namespace Tgstation.Server.Host.Components try { var commandFactory = new CommandFactory(application); - var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory); + var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory, providerFactory); var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); try @@ -131,16 +138,6 @@ namespace Tgstation.Server.Host.Components var chat = chatFactory.CreateChat(metadata.ChatSettings); try { - - foreach (var I in metadata.ChatSettings) - { - //required to be synchrounous - var task = chat.ChangeSettings(I, default); - if (!task.IsCompleted) - throw new InvalidOperationException("Initial chat setup is asynchronous!"); - task.GetAwaiter().GetResult(); - } - var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata()); var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata.CloneMetadata()); var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, metadata.CloneMetadata()); diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 913d4af279..44099741d7 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -167,6 +167,7 @@ namespace Tgstation.Server.Host.Components var dbInstances = databaseContext.Instances.Where(x => x.Online.Value) .Include(x => x.RepositorySettings) .Include(x => x.ChatSettings) + .ThenInclude(x => x.Channels) .Include(x => x.DreamDaemonSettings) .ToAsyncEnumerable(); var tasks = new List(); diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index f835f6d004..5cdc49a972 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -86,7 +86,6 @@ namespace Tgstation.Server.Host.Controllers await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.HttpContext.RequestAborted).ConfigureAwait(false); - var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; var enumerator = Enum.GetValues(typeof(RightsType)); diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 195745ea11..a06c115667 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -77,12 +77,11 @@ namespace Tgstation.Server.Host.Controllers Name = model.Name, ConnectionString = model.ConnectionString, Enabled = model.Enabled, - Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List(), + Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List(), //important that this isn't null InstanceId = Instance.Id, Provider = model.Provider, }; DatabaseContext.ChatSettings.Add(dbModel); - DatabaseContext.ChatChannels.AddRange(dbModel.Channels); try { @@ -116,7 +115,7 @@ namespace Tgstation.Server.Host.Controllers { return BadRequest(new { message = e.Message }); } - return Json(dbModel); + return Json(dbModel.ToApi()); } /// @@ -143,7 +142,7 @@ namespace Tgstation.Server.Host.Controllers foreach (var I in results) I.ConnectionString = null; - return Json(results); + return Json(results.Select(x => x.ToApi())); } /// @@ -197,10 +196,10 @@ namespace Tgstation.Server.Host.Controllers return false; }; - if (!CheckModified(x => x.ConnectionString, ChatSettingsRights.WriteConnectionString) - || !CheckModified(x => x.Enabled, ChatSettingsRights.WriteEnabled) - || !CheckModified(x => x.Name, ChatSettingsRights.WriteName) - || !CheckModified(x => x.Provider, ChatSettingsRights.WriteProvider) + if (CheckModified(x => x.ConnectionString, ChatSettingsRights.WriteConnectionString) + || CheckModified(x => x.Enabled, ChatSettingsRights.WriteEnabled) + || CheckModified(x => x.Name, ChatSettingsRights.WriteName) + || CheckModified(x => x.Provider, ChatSettingsRights.WriteProvider) || (model.Channels != null && !userRights.HasFlag(ChatSettingsRights.WriteChannels))) return Forbid(); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 23ebad9983..1c6a9f666b 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Net; @@ -76,6 +77,18 @@ namespace Tgstation.Server.Host.Controllers model.Path = model.Path.ToUpperInvariant(); } + Models.InstanceUser InstanceAdminUser() => new Models.InstanceUser + { + ByondRights = (ByondRights)~0, + ChatSettingsRights = (ChatSettingsRights)~0, + ConfigurationRights = (ConfigurationRights)~0, + DreamDaemonRights = (DreamDaemonRights)~0, + DreamMakerRights = (DreamMakerRights)~0, + RepositoryRights = (RepositoryRights)~0, + InstanceUserRights = (InstanceUserRights)~0, + UserId = AuthenticationContext.User.Id + }; + /// [TgsAuthorize(InstanceManagerRights.Create)] public override async Task Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) @@ -115,6 +128,11 @@ namespace Tgstation.Server.Host.Controllers CommitterName = application.VersionString, PushTestMergeCommits = false, ShowTestMergeCommitters = true, + }, + //give this user full privileges on the instance + InstanceUsers = new List + { + InstanceAdminUser() } }; @@ -178,9 +196,14 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(InstanceManagerRights.Relocate | InstanceManagerRights.Rename | InstanceManagerRights.SetAutoUpdate | InstanceManagerRights.SetConfiguration | InstanceManagerRights.SetOnline)] public override async Task Update([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) { - var originalModel = await DatabaseContext.Instances.Where(x => x.Id == model.Id) + var instanceQuery = DatabaseContext.Instances.Where(x => x.Id == model.Id); + + var usersInstanceUserTask = instanceQuery.SelectMany(x => x.InstanceUsers).Where(x => x.UserId == AuthenticationContext.User.Id).FirstOrDefaultAsync(cancellationToken); + + var originalModel = await instanceQuery .Include(x => x.RepositorySettings) .Include(x => x.ChatSettings) + .ThenInclude(x => x.Channels) .Include(x => x.DreamDaemonSettings) //need these for onlining .FirstAsync(cancellationToken).ConfigureAwait(false); if (originalModel == default(Models.Instance)) @@ -231,6 +254,13 @@ namespace Tgstation.Server.Host.Controllers || CheckModified(x => x.Online, InstanceManagerRights.SetOnline)) return Forbid(); + //ensure the current user has write privilege on the instance + var usersInstanceUser = await usersInstanceUserTask.ConfigureAwait(false); + if (usersInstanceUser == default) + originalModel.InstanceUsers.Add(InstanceAdminUser()); + else + usersInstanceUser.InstanceUserRights |= InstanceUserRights.WriteUsers; + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); try diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs index ceb71bd530..622771ee6a 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; @@ -31,7 +32,7 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, false) + public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, true) //false instance requirement, we handle this ourself { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -49,14 +50,14 @@ namespace Tgstation.Server.Host.Controllers if (model.InstanceId.HasValue && model.InstanceId != Instance.Id) return BadRequest(new { message = "InstanceId does not match headers!" }); - if (!model.InstanceId.HasValue) + if (!model.UserId.HasValue) return BadRequest(new { message = "Missing UserId!" }); - + return null; } /// - [TgsAuthorize(AdministrationRights.EditUsers)] + [TgsAuthorize(InstanceUserRights.WriteUsers)] public override async Task Create([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken) { var test = StandardModelChecks(model); @@ -71,6 +72,7 @@ namespace Tgstation.Server.Host.Controllers DreamDaemonRights = model.DreamDaemonRights ?? DreamDaemonRights.None, DreamMakerRights = model.DreamMakerRights ?? DreamMakerRights.None, RepositoryRights = model.RepositoryRights ?? RepositoryRights.None, + InstanceUserRights = model.InstanceUserRights ?? InstanceUserRights.None, UserId = model.UserId }; @@ -95,7 +97,7 @@ namespace Tgstation.Server.Host.Controllers } /// - [TgsAuthorize(AdministrationRights.EditUsers)] + [TgsAuthorize(InstanceUserRights.WriteUsers)] public override async Task Update([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken) { var test = StandardModelChecks(model); @@ -131,7 +133,7 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(InstanceUserRights.ReadUsers)] public override async Task List(CancellationToken cancellationToken) { - var users = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).ToListAsync(cancellationToken).ConfigureAwait(false); + var users = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Include(x => x.InstanceUsers).SelectMany(x => x.InstanceUsers).ToListAsync(cancellationToken).ConfigureAwait(false); return Json(users.Select(x => x.ToApi())); } @@ -147,11 +149,9 @@ namespace Tgstation.Server.Host.Controllers } /// - [TgsAuthorize(AdministrationRights.EditUsers)] + [TgsAuthorize(InstanceUserRights.WriteUsers)] public override async Task Delete(long id, CancellationToken cancellationToken) { - //id is actually UserId - await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).DeleteAsync(cancellationToken).ConfigureAwait(false); return Ok(); } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index fae43df35a..52b6b4364d 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -20,6 +20,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; @@ -193,6 +194,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(new ByondTopicSender { ReceiveTimeout = 5000, diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index 3bd53864fd..09fcf41df6 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -1,7 +1,9 @@ -namespace Tgstation.Server.Host.Models +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Models { /// - public sealed class ChatChannel : Api.Models.ChatChannel + public sealed class ChatChannel : Api.Models.ChatChannel, IApiConvertable { /// /// The row Id @@ -17,5 +19,14 @@ /// The /// public ChatSettings ChatSettings { get; set; } + + /// + public Api.Models.ChatChannel ToApi() => new Api.Models.ChatChannel + { + DiscordChannelId = DiscordChannelId, + IsAdminChannel = IsAdminChannel, + IsWatchdogChannel = IsWatchdogChannel, + IrcChannel = IrcChannel + }; } } diff --git a/src/Tgstation.Server.Host/Models/ChatSettings.cs b/src/Tgstation.Server.Host/Models/ChatSettings.cs index 1152181268..816139e347 100644 --- a/src/Tgstation.Server.Host/Models/ChatSettings.cs +++ b/src/Tgstation.Server.Host/Models/ChatSettings.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class ChatSettings : Api.Models.Internal.ChatSettings + public sealed class ChatSettings : Api.Models.Internal.ChatSettings, IApiConvertable { /// /// The @@ -21,5 +23,16 @@ namespace Tgstation.Server.Host.Models /// See /// public List Channels { get; set; } + + /// + public Api.Models.ChatSettings ToApi() => new Api.Models.ChatSettings + { + Channels = Channels.Select(x => x.ToApi()).ToList(), + ConnectionString = ConnectionString, + Enabled = Enabled, + Provider = Provider, + Id = Id, + Name = Name + }; } } diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 43d9fae655..813bf40d0e 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -24,7 +24,8 @@ namespace Tgstation.Server.Host.Models ChatSettingsRights != Api.Rights.ChatSettingsRights.None || ConfigurationRights != Api.Rights.ConfigurationRights.None || DreamDaemonRights != Api.Rights.DreamDaemonRights.None || - DreamMakerRights != Api.Rights.DreamMakerRights.None; + DreamMakerRights != Api.Rights.DreamMakerRights.None || + InstanceUserRights != Api.Rights.InstanceUserRights.None; /// public Api.Models.InstanceUser ToApi() => new Api.Models.InstanceUser @@ -36,6 +37,7 @@ namespace Tgstation.Server.Host.Models DreamMakerRights = DreamMakerRights, InstanceId = InstanceId, RepositoryRights = RepositoryRights, + InstanceUserRights = InstanceUserRights, UserId = UserId }; } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 587530c022..853dba9481 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -47,16 +47,17 @@ namespace Tgstation.Server.Host.Security if (CurrentAuthenticationContext != null) throw new InvalidOperationException("Authentication context has already been loaded"); - var userQuery = databaseContext.Users.Where(x => x.Id == userId); + var userQuery = databaseContext.Users.Where(x => x.Id == userId).FirstAsync(cancellationToken); - if (instanceId.HasValue) - userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId)); + var instanceUser = instanceId.HasValue ? (await databaseContext.Users + .Where(x => x.Id == userId) + .Include(x => x.InstanceUsers) + .SelectMany(x => x.InstanceUsers) + .Where(x => x.InstanceId == instanceId && x.Instance.Online.Value) + .Include(x => x.Instance) + .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false)) : null; - var user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false); - - InstanceUser instanceUser = null; - if (instanceId.HasValue) - instanceUser = user.InstanceUsers.Where(x => x.InstanceId == instanceId).FirstOrDefault(); + var user = await userQuery.ConfigureAwait(false); ISystemIdentity systemIdentity; if (user.SystemIdentifier != null) diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index 2f26a89582..36e006c03c 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -621,6 +621,278 @@ "name": "Instance", "description": null, "item": [ + { + "name": "Chat", + "description": "", + "item": [ + { + "name": "Make ID 1 use NS auth", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": \"1\",\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0;0;asdfasdf\"\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, + { + "name": "Create tgs4_test on #coderbus Copy", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"Rizon\",\n\t\"enabled\": true,\n\t\"provider\": 0,\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0\",\n\t\"channels\": [\n\t\t{\n\t\t\t\"ircChannel\": \"#coderbus\",\n\t\t\t\"isAdminChannel\": true,\n\t\t\t\"isWatchdogChannel\": true\n\t\t}\n\t\t]\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, + { + "name": "Delete Rizon", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": {}, + "url": { + "raw": "localhost:5000/Chat/1", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat", + "1" + ] + } + }, + "response": [] + }, + { + "name": "List connections", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": true\n}" + }, + "url": { + "raw": "localhost:5000/Chat/List", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat", + "List" + ] + } + }, + "response": [] + } + ], + "_postman_isSubFolder": true + }, + { + "name": "Users", + "description": "", + "item": [ + { + "name": "Add admin to Instace ID 1 all perms", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": -1,\n\t\"dreamDaemonRights\": -1,\n\t\"dreamMakerRights\": -1,\n\t\"repositoryRights\": -1,\n\t\"chatSettingsRights\": -1,\n\t\"configurationRights\": -1\n}" + }, + "url": { + "raw": "localhost:5000/InstanceUser", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "InstanceUser" + ] + } + }, + "response": [] + }, + { + "name": "List Instance ID 1 Users", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": -1,\n\t\"dreamDaemonRights\": -1,\n\t\"dreamMakerRights\": -1,\n\t\"repositoryRights\": -1,\n\t\"chatSettingsRights\": -1,\n\t\"configurationRights\": -1\n}" + }, + "url": { + "raw": "localhost:5000/InstanceUser/List", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "InstanceUser", + "List" + ] + } + }, + "response": [] + } + ], + "_postman_isSubFolder": true + }, { "name": "Online Instance ID 1", "request": { @@ -702,7 +974,7 @@ { "name": "List instances", "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Accept", @@ -726,20 +998,21 @@ "raw": "{\n \"id\": 1,\n \"online\": false\n}" }, "url": { - "raw": "localhost:5000/Instance", + "raw": "localhost:5000/Instance/List", "host": [ "localhost" ], "port": "5000", "path": [ - "Instance" + "Instance", + "List" ] } }, "response": [] }, { - "name": "Create Instance TestInstance Copy", + "name": "Create Instance TestInstance", "request": { "method": "PUT", "header": [ @@ -905,7 +1178,7 @@ "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjcxNjIzMSIsIm5iZiI6IjE1MzI3MTI2MzEiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.Kyuh1sX51BecGNcD8WiL_mXpgSTwkMV2EmVwoLBoxWo", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjcyNzE5NSIsIm5iZiI6IjE1MzI3MjM1OTUiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.VZIkaVQS0JRktRUx7yHVJdtFL8p_LcpF31zNW8SZ7Zc", "type": "string" } ] diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index f1e606e7b5..0e1aebcf4c 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -16,3 +16,7 @@ Verify the byond cache folder location on linux Map hosting address and certificate directly from configuration Server updates. Use FOLDERS so dependencies can be properly packaged + +TgsAuthorize attribute |'ing doesn't work due to role names, change to params[] + +Chat straight doesn't work, fix it \ No newline at end of file From 21d9a7b871a07bb16069204fa0bc193bf1911cdd Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 10:35:28 -0400 Subject: [PATCH 350/397] Fix the chat handler crashing --- src/Tgstation.Server.Host/Components/Chat/Chat.cs | 13 ++++++++++--- v4_prototype_TODO.txt | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index be8fed89fd..4f3d2ba469 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -238,18 +238,25 @@ namespace Tgstation.Server.Host.Components.Chat //wait for a message await Task.WhenAny(messageTasks.Select(x => x.Value)).ConfigureAwait(false); - //process completed ones + var toRemove = new List(); + //process completed ones foreach (var I in messageTasks.Where(x => x.Value.IsCompleted)) { - messageTasks.Remove(I.Key); - var message = await I.Value.ConfigureAwait(false); await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); + + toRemove.Add(I.Key); } + foreach (var I in toRemove) + messageTasks.Remove(I); } } catch (OperationCanceledException) { } + catch(Exception e) + { + logger.LogError("Message monitor crashed!: Exception: {0}", e); + } } /// diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 0e1aebcf4c..745b097214 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -19,4 +19,6 @@ Server updates. Use FOLDERS so dependencies can be properly packaged TgsAuthorize attribute |'ing doesn't work due to role names, change to params[] -Chat straight doesn't work, fix it \ No newline at end of file +Chat straight doesn't work, fix it + +Don't throw arg null exceptions with null [FromBody]'s, apparently that's allowed, return bad request instead \ No newline at end of file From bc0a19e8ab55911f38442ac49407cd1707ec0f69 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 13:27:57 -0400 Subject: [PATCH 351/397] Fix chat for IRC at least --- .../Rights/ChatSettingsRights.cs | 2 +- .../Components/Chat/Chat.cs | 124 ++++++++++++++---- .../Components/Chat/Providers/IrcProvider.cs | 57 +++++--- .../Controllers/ChatController.cs | 10 +- .../Tgstation.Server.Host.csproj | 2 +- 5 files changed, 147 insertions(+), 48 deletions(-) diff --git a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs index c4336d76dd..a071e2bf38 100644 --- a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs @@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Rights /// WriteConnectionString = 8, /// - /// User can read + /// User can read requires /// ReadConnectionString = 16, /// diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 4f3d2ba469..8748e414a8 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -132,8 +133,8 @@ namespace Tgstation.Server.Host.Components.Chat Task task; lock (mappedChannels) { - foreach (var kvp in mappedChannels.Where(x => x.Value.ProviderId == connectionId)) - mappedChannels.Remove(kvp.Key); + foreach (var I in mappedChannels.Where(x => x.Value.ProviderId == connectionId).Select(x => x.Key).ToList()) + mappedChannels.Remove(I); if (updateTrackings) lock (trackingContexts) @@ -156,6 +157,32 @@ namespace Tgstation.Server.Host.Components.Chat { logger.LogTrace("Chat message: {0}. User (Note unconverted provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User)); + //map the channel if it's private and we haven't seen it + if (message.User.Channel.IsPrivate) + lock (providers) + lock (mappedChannels) + { + if (!provider.Connected) + return; + var enumerable = mappedChannels.Where(x => x.Value.ProviderChannelId == message.User.Channel.RealId); + if (!enumerable.Any()) + { + ulong newId; + lock (this) + newId = channelIdCounter++; + mappedChannels.Add(newId, new ChannelMapping + { + IsWatchdogChannel = false, + ProviderChannelId = message.User.Channel.RealId, + ProviderId = providers.Where(x => x.Value == provider).Select(x => x.Key).First(), + Channel = message.User.Channel + }); + message.User.Channel.RealId = newId; + } + else + message.User.Channel.RealId = enumerable.First().Key; + } + var splits = new List(message.Content.TrimEnd().Split(' ')); var address = splits[0]; if (address.Length > 1 && (address[address.Length - 1] == ':' || address[address.Length - 1] == ',')) @@ -163,18 +190,21 @@ namespace Tgstation.Server.Host.Components.Chat address = address.ToUpperInvariant(); - if (address != CommonMention.ToUpperInvariant() && address != provider.BotMention.ToUpperInvariant()) + var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant(); + + if (!addressed && !message.User.Channel.IsPrivate) //no mention return; - if (splits.Count == 1) + if ((splits.Count == 1 && !message.User.Channel.IsPrivate) || splits.Count == 0) { //just a mention await SendMessage("Hi!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } - splits.RemoveAt(0); + if (addressed) + splits.RemoveAt(0); var command = splits[0].ToUpperInvariant(); splits.RemoveAt(0); @@ -182,16 +212,46 @@ namespace Tgstation.Server.Host.Components.Chat try { - if (!builtinCommands.TryGetValue(command, out ICommand commandHandler)) + async Task GetCommand(string commandName) { - var tasks = trackingContexts.Select(x => x.GetCustomCommands(cancellationToken)); - await Task.WhenAll(tasks).ConfigureAwait(false); - commandHandler = tasks.SelectMany(x => x.Result).Where(x => x.Name.ToUpperInvariant() == command).FirstOrDefault(); + if (!builtinCommands.TryGetValue(commandName, out var handler)) + { + var tasks = trackingContexts.Select(x => x.GetCustomCommands(cancellationToken)); + 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."; + + if (command == "HELP" || command == "?") + { + string helpText; + if (splits.Count == 0) + { + var allCommands = builtinCommands.Select(x => x.Value).ToList(); + var tasks = trackingContexts.Select(x => x.GetCustomCommands(cancellationToken)); + await Task.WhenAll(tasks).ConfigureAwait(false); + allCommands.AddRange(tasks.SelectMany(x => x.Result)); + helpText = String.Format(CultureInfo.InvariantCulture, "Available commands: Type 'help' and then a command name for more details: {0}", String.Join(", ", allCommands.Select(x => x.Name))); + } + else + { + var helpHandler = await GetCommand(splits[0].ToUpperInvariant()).ConfigureAwait(false); + if (helpHandler != default) + helpText = String.Format(CultureInfo.InvariantCulture, "{0}: {1}", helpHandler.Name, helpHandler.HelpText); + else + helpText = UnknownCommandMessage; + } + await SendMessage(helpText, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); + return; } - if (command == default) + var commandHandler = await GetCommand(command).ConfigureAwait(false); + if (commandHandler == default) { - await SendMessage("Invalid command! Type '?' or 'help' for available commands.", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); + await SendMessage(UnknownCommandMessage, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } @@ -220,9 +280,8 @@ namespace Tgstation.Server.Host.Components.Chat while (!cancellationToken.IsCancellationRequested) { //prune disconnected providers - foreach (var I in messageTasks) - if (!I.Key.Connected) - messageTasks.Remove(I.Key); + foreach (var I in messageTasks.Where(x => !x.Key.Connected).ToList()) + messageTasks.Remove(I.Key); //add new ones foreach (var I in providers) @@ -237,19 +296,16 @@ namespace Tgstation.Server.Host.Components.Chat //wait for a message await Task.WhenAny(messageTasks.Select(x => x.Value)).ConfigureAwait(false); - - var toRemove = new List(); - //process completed ones - foreach (var I in messageTasks.Where(x => x.Value.IsCompleted)) + + //process completed ones + foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) { var message = await I.Value.ConfigureAwait(false); await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); - toRemove.Add(I.Key); + messageTasks.Remove(I.Key); } - foreach (var I in toRemove) - messageTasks.Remove(I); } } catch (OperationCanceledException) { } @@ -310,23 +366,43 @@ namespace Tgstation.Server.Host.Components.Chat if (newSettings == null) throw new ArgumentNullException(nameof(newSettings)); IProvider provider; + + async Task DisconnectProvider(IProvider p) + { + try + { + await p.Disconnect(cancellationToken).ConfigureAwait(false); + } + finally + { + p.Dispose(); + } + } + + Task disconnectTask; lock (providers) { //raw settings changes forces a rebuild of the provider if (providers.TryGetValue(newSettings.Id, out provider)) { providers.Remove(newSettings.Id); - provider.Dispose(); + disconnectTask = DisconnectProvider(provider); } + else + disconnectTask = Task.CompletedTask; if (newSettings.Enabled.Value) { provider = providerFactory.CreateProvider(newSettings); providers.Add(newSettings.Id, provider); } } + lock (mappedChannels) - foreach (var channelId in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key)) - mappedChannels.Remove(channelId); + foreach (var I in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key).ToList()) + mappedChannels.Remove(I); + + await disconnectTask.ConfigureAwait(false); + if (newSettings.Enabled.Value && started) await provider.Connect(cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 557b980fd8..adb436d08a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -133,7 +133,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override void Dispose() => client.Disconnect(); //just closes the socket + public override void Dispose() + { + if(Connected) + client.Disconnect(); //just closes the socket + } /// /// Handle an IRC message @@ -142,10 +146,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// If this is a query message void HandleMessage(IrcEventArgs e, bool isPrivate) { - if (e.Data.From.ToUpperInvariant() == client.Nickname.ToUpperInvariant()) + if (e.Data.Nick.ToUpperInvariant() == client.Nickname.ToUpperInvariant()) return; - var username = e.Data.From; + var username = e.Data.Nick; var channelName = isPrivate ? username : e.Data.Channel; ulong channelId = 0; lock (this) @@ -279,19 +283,26 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { if (!Connected) return; - await Task.Factory.StartNew(() => + try { - try + await Task.Factory.StartNew(() => { - client.RfcQuit(); - } - catch (Exception e) - { - logger.LogWarning("Error quitting IRC: {0}", e); - } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - Dispose(); - await listenTask.ConfigureAwait(false); + try + { + client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); //priocritical otherwise Disconnect will hard block + } + catch (Exception e) + { + logger.LogWarning("Error quitting IRC: {0}", e); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + Dispose(); + await listenTask.ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning("Error disconnecting from IRC! Exception: {0}", e); + } } /// @@ -315,7 +326,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers client.RfcJoin(I); return (IReadOnlyList)channels.Select(x => { - ulong id = channelIdCounter; + var id = channelIdCounter; if (!channelIdMap.Any(y => { if (y.Value != x.IrcChannel) @@ -323,9 +334,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers id = y.Key; return true; })) + { channelIdMap.Add(id, x.IrcChannel); - else ++channelIdCounter; + } return new Channel { RealId = id, @@ -341,11 +353,18 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public override Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - var channelName = channelIdMap[channelId] ?? queryChannelIdMap[channelId]; + var channelName = channelIdMap[channelId]; + SendType sendType; + if (channelName == null) + { + channelName = queryChannelIdMap[channelId]; + sendType = SendType.Notice; + } + else + sendType = SendType.Message; try { - if (client.JoinedChannels.Contains(channelName)) - client.SendMessage(SendType.Message, channelName, message); + client.SendMessage(sendType, channelName, message); } catch(Exception e) { diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index a06c115667..ebbdd55548 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -219,11 +219,15 @@ namespace Tgstation.Server.Host.Controllers //have to rebuild the thing first await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false); - if (model.Channels != null) + if (model.Channels != null || anySettingsModified) await chat.ChangeChannels(current.Id, current.Channels, cancellationToken).ConfigureAwait(false); - if(userRights.HasFlag(ChatSettingsRights.Read)) - return Json(current); + if (userRights.HasFlag(ChatSettingsRights.Read)) + { + if (!userRights.HasFlag(ChatSettingsRights.ReadConnectionString)) + current.ConnectionString = null; + return Json(current.ToApi()); + } return Ok(); } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index f7ba838484..3643b12614 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -36,7 +36,7 @@ - + From 278468c380c5c536050aefbe3ca68c9b8b419fad Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 13:28:23 -0400 Subject: [PATCH 352/397] Buffs postman with prerequest script --- tools/TGS.postman_collection.json | 117 ++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index 36e006c03c..835c49d8c9 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "a318afb1-1fa5-4d13-b007-9fb6ab4930e0", + "_postman_id": "69222fa3-00a7-4bfe-91ad-a41a379d6181", "name": "TGS", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -623,7 +623,7 @@ "item": [ { "name": "Chat", - "description": "", + "description": null, "item": [ { "name": "Make ID 1 use NS auth", @@ -653,7 +653,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"id\": \"1\",\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0;0;asdfasdf\"\n}" + "raw": "{\n\t\"id\": \"1\",\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0;2;asdfasdf\"\n}" }, "url": { "raw": "localhost:5000/Chat", @@ -669,7 +669,50 @@ "response": [] }, { - "name": "Create tgs4_test on #coderbus Copy", + "name": "Make ID 1 use SASL auth", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": \"1\",\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0;1;asdfasdf\"\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, + { + "name": "Create tgs4_test on #botbus", "request": { "method": "PUT", "header": [ @@ -711,6 +754,49 @@ }, "response": [] }, + { + "name": "Add #coderbus to ID 1", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 1,\n\t\"channels\": [\n\t\t{\n\t\t\t\"ircChannel\": \"#coderbus\",\n\t\t\t\"isAdminChannel\": true,\n\t\t\t\"isWatchdogChannel\": true\n\t\t},\n\t\t{\n\t\t\t\"ircChannel\": \"#botbus\",\n\t\t\t\"isAdminChannel\": true,\n\t\t\t\"isWatchdogChannel\": true\n\t\t}\n\t\t]\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, { "name": "Delete Rizon", "request": { @@ -737,7 +823,10 @@ "value": "1" } ], - "body": {}, + "body": { + "mode": "raw", + "raw": "" + }, "url": { "raw": "localhost:5000/Chat/1", "host": [ @@ -801,7 +890,7 @@ }, { "name": "Users", - "description": "", + "description": null, "item": [ { "name": "Add admin to Instace ID 1 all perms", @@ -1178,7 +1267,7 @@ "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjcyNzE5NSIsIm5iZiI6IjE1MzI3MjM1OTUiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.VZIkaVQS0JRktRUx7yHVJdtFL8p_LcpF31zNW8SZ7Zc", + "value": "{{currentAccessToken}}", "type": "string" } ] @@ -1190,7 +1279,19 @@ "id": "cb655975-15d0-4115-9849-4493db7593c1", "type": "text/javascript", "exec": [ - "" + "const echoPostRequest = {", + " url: 'http://localhost:5000',", + " method: 'POST',", + " header: ['Accept:application/json', \"User-Agent:Postman/1.0\", \"User-Agent:Tgstation.Server.Api/4.0.0.0\", \"Authorization:Password ISolemlySwearToDeleteTheDataDirectory\", \"Username:admin\"]", + "};", + "", + "pm.sendRequest(echoPostRequest, function (err, res) {", + "console.log(err ? err : res.json());", + " if (err === null) {", + " var responseJson = res.json();", + " pm.environment.set('currentAccessToken', responseJson.bearer)", + " }", + "});" ] } }, From 921e490198d34328b98926e385f88a0e6b3c6336 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 16:09:06 -0400 Subject: [PATCH 353/397] Chat works now, yay! --- .../Components/Chat/Chat.cs | 42 +++- .../Chat/Providers/DiscordProvider.cs | 16 +- .../Controllers/ChatController.cs | 9 +- tools/TGS.postman_collection.json | 219 +++++++++++++++++- v4_prototype_TODO.txt | 2 - 5 files changed, 264 insertions(+), 24 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 8748e414a8..6dc8f86a6f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -73,6 +73,11 @@ namespace Tgstation.Server.Host.Components.Chat /// Task chatHandler; + /// + /// The that completes when change + /// + TaskCompletionSource connectionsUpdated; + /// /// Used for remapping s /// @@ -106,6 +111,7 @@ namespace Tgstation.Server.Host.Components.Chat mappedChannels = new Dictionary(); trackingContexts = new List(); handlerCts = new CancellationTokenSource(); + connectionsUpdated = new TaskCompletionSource(); channelIdCounter = 1; } @@ -196,16 +202,16 @@ namespace Tgstation.Server.Host.Components.Chat //no mention return; - if ((splits.Count == 1 && !message.User.Channel.IsPrivate) || splits.Count == 0) + if (addressed) + splits.RemoveAt(0); + + if ((splits.Count == 1 && (!message.User.Channel.IsPrivate || splits[0].Length == 0)) || splits.Count == 0) { //just a mention await SendMessage("Hi!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } - if (addressed) - splits.RemoveAt(0); - var command = splits[0].ToUpperInvariant(); splits.RemoveAt(0); var arguments = String.Join(" ", splits); @@ -234,7 +240,7 @@ namespace Tgstation.Server.Host.Components.Chat var tasks = trackingContexts.Select(x => x.GetCustomCommands(cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false); allCommands.AddRange(tasks.SelectMany(x => x.Result)); - helpText = String.Format(CultureInfo.InvariantCulture, "Available commands: Type 'help' and then a command name for more details: {0}", String.Join(", ", allCommands.Select(x => x.Name))); + helpText = String.Format(CultureInfo.InvariantCulture, "Available commands (Type '?' or 'help' and then a command name for more details): {0}", String.Join(", ", allCommands.Select(x => x.Name))); } else { @@ -284,9 +290,13 @@ namespace Tgstation.Server.Host.Components.Chat messageTasks.Remove(I.Key); //add new ones - foreach (var I in providers) - if (I.Value.Connected && !messageTasks.ContainsKey(I.Value)) - messageTasks.Add(I.Value, I.Value.NextMessage(cancellationToken)); + Task updatedTask; + lock (this) + updatedTask = connectionsUpdated.Task; + lock (providers) + foreach (var I in providers) + if (I.Value.Connected && !messageTasks.ContainsKey(I.Value)) + messageTasks.Add(I.Value, I.Value.NextMessage(cancellationToken)); if(messageTasks.Count == 0) { @@ -295,7 +305,7 @@ namespace Tgstation.Server.Host.Components.Chat } //wait for a message - await Task.WhenAny(messageTasks.Select(x => x.Value)).ConfigureAwait(false); + await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value))).ConfigureAwait(false); //process completed ones foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) @@ -403,8 +413,18 @@ namespace Tgstation.Server.Host.Components.Chat await disconnectTask.ConfigureAwait(false); - if (newSettings.Enabled.Value && started) - await provider.Connect(cancellationToken).ConfigureAwait(false); + if (started) + { + if (newSettings.Enabled.Value) + await provider.Connect(cancellationToken).ConfigureAwait(false); + lock(this) + { + //same thread shennanigans + var oldOne = connectionsUpdated; + connectionsUpdated = new TaskCompletionSource(); + oldOne.SetResult(null); + } + } } /// diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 7b4694d574..2bb8dcff06 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { if (!Connected) throw new InvalidOperationException("Provider not connected"); - return client.CurrentUser.Mention; + return NormalizeMention(client.CurrentUser.Mention); } } @@ -49,6 +49,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// readonly List mappedChannels; + static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", ""); + /// /// Construct a /// @@ -73,13 +75,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// A representing the running operation Task Client_MessageReceived(SocketMessage e) { - if (e.Author.Id != client.CurrentUser.Id) + if (e.Author.Id == client.CurrentUser.Id) return Task.CompletedTask; var pm = e.Channel is IPrivateChannel; if (!pm && !mappedChannels.Contains(e.Channel.Id)) - return Task.CompletedTask; + return e.MentionedUsers.Any(x => x.Id == client.CurrentUser.Id) ? SendMessage(e.Channel.Id, "I do not respond to this channel!", default) : Task.CompletedTask; var result = new Message { Content = e.Content, @@ -95,7 +97,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers FriendlyName = e.Channel.Name }, FriendlyName = e.Author.Username, - Mention = e.Author.Mention + Mention = NormalizeMention(e.Author.Mention) } }; EnqueueMessage(result); @@ -190,10 +192,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) { + public override async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) + { try { - await ((client.GetChannel(channelId) as ITextChannel)?.SendMessageAsync(message, false, null, new RequestOptions + var channel = client.GetChannel(channelId) as IMessageChannel; + await (channel?.SendMessageAsync(message, false, null, new RequestOptions { CancelToken = cancellationToken }) ?? Task.CompletedTask).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index ebbdd55548..f4347503d7 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -151,15 +151,16 @@ namespace Tgstation.Server.Host.Controllers { var query = DatabaseContext.ChatSettings.Where(x => x.Id == id).Include(x => x.Channels); - var results = await query.ToListAsync(cancellationToken).ConfigureAwait(false); + var results = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (results == default) + return NotFound(); var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatSettings) & (int)ChatSettingsRights.ReadConnectionString) != 0; if (!connectionStrings) - foreach (var I in results) - I.ConnectionString = null; + results.ConnectionString = null; - return Json(results); + return Json(results.ToApi()); } /// diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index 835c49d8c9..d3692a376d 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -668,6 +668,94 @@ }, "response": [] }, + { + "name": "Get ID 1", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": \"1\",\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0;2;asdfasdf\"\n}" + }, + "url": { + "raw": "localhost:5000/Chat/1", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Get ID 2", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": \"1\",\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0;2;asdfasdf\"\n}" + }, + "url": { + "raw": "localhost:5000/Chat/2", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat", + "2" + ] + } + }, + "response": [] + }, { "name": "Make ID 1 use SASL auth", "request": { @@ -739,7 +827,93 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"Rizon\",\n\t\"enabled\": true,\n\t\"provider\": 0,\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0\",\n\t\"channels\": [\n\t\t{\n\t\t\t\"ircChannel\": \"#coderbus\",\n\t\t\t\"isAdminChannel\": true,\n\t\t\t\"isWatchdogChannel\": true\n\t\t}\n\t\t]\n}" + "raw": "{\n\t\"name\": \"Rizon\",\n\t\"enabled\": true,\n\t\"provider\": 0,\n\t\"connectionString\": \"irc.rizon.net;6667;tgs4_test;0\",\n\t\"channels\": [\n\t\t{\n\t\t\t\"ircChannel\": \"#botbus\",\n\t\t\t\"isAdminChannel\": true,\n\t\t\t\"isWatchdogChannel\": true\n\t\t}\n\t\t]\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, + { + "name": "Create r4407 on Disord", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"r4407\",\n\t\"enabled\": false,\n\t\"provider\": 1,\n\t\"connectionString\": \"NDIwMDg3NDczOTY0NzExOTQ3.Dj-K1w.3Hj3YQKzmpeM7DKz2M7JEJlzdjs\"\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, + { + "name": "Enable ID 2", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 2,\n\t\"enabled\": true\n}" }, "url": { "raw": "localhost:5000/Chat", @@ -797,6 +971,49 @@ }, "response": [] }, + { + "name": "Add #extranet to ID 2", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 2,\n\t\"channels\": [\n\t\t{\n\t\t\t\"discordChannelId\": 164876336958144512,\n\t\t\t\"isAdminChannel\": true,\n\t\t\t\"isWatchdogChannel\": true\n\t\t}\n\t\t]\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, { "name": "Delete Rizon", "request": { diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 745b097214..fe4a87603e 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -19,6 +19,4 @@ Server updates. Use FOLDERS so dependencies can be properly packaged TgsAuthorize attribute |'ing doesn't work due to role names, change to params[] -Chat straight doesn't work, fix it - Don't throw arg null exceptions with null [FromBody]'s, apparently that's allowed, return bad request instead \ No newline at end of file From dd394d979537c7bfe7c5a44e99c2cae794f3fe9b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 16:32:52 -0400 Subject: [PATCH 354/397] Further subdivision of the Components namespace --- .../{ => Byond}/ByondExecutableLock.cs | 2 +- .../Components/{ => Byond}/ByondManager.cs | 5 +---- .../{ => Byond}/IByondExecutableLock.cs | 2 +- .../Components/{ => Byond}/IByondInstaller.cs | 2 +- .../Components/{ => Byond}/IByondManager.cs | 2 +- .../{ => Byond}/PosixByondInstaller.cs | 2 +- .../{ => Byond}/WindowsByondInstaller.cs | 2 +- .../Components/{ => Compiler}/DmbFactory.cs | 2 +- .../Components/{ => Compiler}/DmbProvider.cs | 2 +- .../Components/{ => Compiler}/DreamMaker.cs | 18 ++++++++++----- .../Compiler/ICompileJobConsumer.cs | 22 +++++++++++++++++++ .../Components/{ => Compiler}/IDmbFactory.cs | 2 +- .../Components/{ => Compiler}/IDreamMaker.cs | 3 ++- .../{ => Compiler}/TemporaryDmbProvider.cs | 2 +- .../Components/ICompileJobConsumer.cs | 13 ----------- .../Components/IInstance.cs | 10 ++++++--- .../Components/Instance.cs | 21 ++++++++++++++++-- .../Components/InstanceFactory.cs | 5 ++++- .../Components/ReattachInfoHandler.cs | 1 + .../{ => Repository}/IRepository.cs | 2 +- .../{ => Repository}/IRepositoryManager.cs | 2 +- .../Components/{ => Repository}/Repository.cs | 2 +- .../{ => Repository}/RepositoryManager.cs | 2 +- .../{ => StaticFiles}/Configuration.cs | 2 +- .../{ => StaticFiles}/IConfiguration.cs | 2 +- .../ServerSideModifications.cs | 4 +--- .../Components/Watchdog/Executor.cs | 1 + .../Components/Watchdog/IExecutor.cs | 1 + .../Watchdog/ISessionControllerFactory.cs | 1 + .../Components/Watchdog/IWatchdogFactory.cs | 1 + .../Watchdog/ReattachInformation.cs | 3 ++- .../Components/Watchdog/Session.cs | 1 + .../Watchdog/SessionControllerFactory.cs | 1 + .../Components/Watchdog/Watchdog.cs | 1 + .../Components/Watchdog/WatchdogFactory.cs | 1 + .../Watchdog/WatchdogReattachInformation.cs | 2 +- .../Controllers/DreamMakerController.cs | 2 +- src/Tgstation.Server.Host/Core/Application.cs | 1 + 38 files changed, 98 insertions(+), 52 deletions(-) rename src/Tgstation.Server.Host/Components/{ => Byond}/ByondExecutableLock.cs (92%) rename src/Tgstation.Server.Host/Components/{ => Byond}/ByondManager.cs (98%) rename src/Tgstation.Server.Host/Components/{ => Byond}/IByondExecutableLock.cs (93%) rename src/Tgstation.Server.Host/Components/{ => Byond}/IByondInstaller.cs (97%) rename src/Tgstation.Server.Host/Components/{ => Byond}/IByondManager.cs (95%) rename src/Tgstation.Server.Host/Components/{ => Byond}/PosixByondInstaller.cs (98%) rename src/Tgstation.Server.Host/Components/{ => Byond}/WindowsByondInstaller.cs (98%) rename src/Tgstation.Server.Host/Components/{ => Compiler}/DmbFactory.cs (99%) rename src/Tgstation.Server.Host/Components/{ => Compiler}/DmbProvider.cs (97%) rename src/Tgstation.Server.Host/Components/{ => Compiler}/DreamMaker.cs (93%) create mode 100644 src/Tgstation.Server.Host/Components/Compiler/ICompileJobConsumer.cs rename src/Tgstation.Server.Host/Components/{ => Compiler}/IDmbFactory.cs (95%) rename src/Tgstation.Server.Host/Components/{ => Compiler}/IDreamMaker.cs (92%) rename src/Tgstation.Server.Host/Components/{ => Compiler}/TemporaryDmbProvider.cs (95%) delete mode 100644 src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs rename src/Tgstation.Server.Host/Components/{ => Repository}/IRepository.cs (98%) rename src/Tgstation.Server.Host/Components/{ => Repository}/IRepositoryManager.cs (95%) rename src/Tgstation.Server.Host/Components/{ => Repository}/Repository.cs (99%) rename src/Tgstation.Server.Host/Components/{ => Repository}/RepositoryManager.cs (98%) rename src/Tgstation.Server.Host/Components/{ => StaticFiles}/Configuration.cs (99%) rename src/Tgstation.Server.Host/Components/{ => StaticFiles}/IConfiguration.cs (98%) rename src/Tgstation.Server.Host/Components/{ => StaticFiles}/ServerSideModifications.cs (94%) diff --git a/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs similarity index 92% rename from src/Tgstation.Server.Host/Components/ByondExecutableLock.cs rename to src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs index 2be7ab498a..3431ede531 100644 --- a/src/Tgstation.Server.Host/Components/ByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs @@ -1,6 +1,6 @@ using System; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Byond { /// sealed class ByondExecutableLock : IByondExecutableLock diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs similarity index 98% rename from src/Tgstation.Server.Host/Components/ByondManager.cs rename to src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 70af749930..f6f0c44790 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -1,16 +1,13 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.IO.Compression; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.IO; -using MemoryStream = System.IO.MemoryStream; - -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Byond { /// sealed class ByondManager : IByondManager diff --git a/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs similarity index 93% rename from src/Tgstation.Server.Host/Components/IByondExecutableLock.cs rename to src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs index 2d390534e2..76e3c04321 100644 --- a/src/Tgstation.Server.Host/Components/IByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondExecutableLock.cs @@ -1,6 +1,6 @@ using System; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Byond { /// /// Represents usage of the two primary BYOND server executables diff --git a/src/Tgstation.Server.Host/Components/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs similarity index 97% rename from src/Tgstation.Server.Host/Components/IByondInstaller.cs rename to src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs index 9ad7e26510..8c2e742b30 100644 --- a/src/Tgstation.Server.Host/Components/IByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondInstaller.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Byond { /// /// For downloading and installing BYOND extractions for a given system diff --git a/src/Tgstation.Server.Host/Components/IByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs similarity index 95% rename from src/Tgstation.Server.Host/Components/IByondManager.cs rename to src/Tgstation.Server.Host/Components/Byond/IByondManager.cs index c6ac12119e..70fd934081 100644 --- a/src/Tgstation.Server.Host/Components/IByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs @@ -3,7 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Byond { /// /// For managing the BYOND installation diff --git a/src/Tgstation.Server.Host/Components/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs similarity index 98% rename from src/Tgstation.Server.Host/Components/PosixByondInstaller.cs rename to src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs index 2a3e5cd44d..4bbcc351a2 100644 --- a/src/Tgstation.Server.Host/Components/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.IO; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Byond { /// /// for Posix systems diff --git a/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs similarity index 98% rename from src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs rename to src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 6b102ace09..e1f656e0e2 100644 --- a/src/Tgstation.Server.Host/Components/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.IO; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Byond { /// /// for windows systems diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs similarity index 99% rename from src/Tgstation.Server.Host/Components/DmbFactory.cs rename to src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index 0cb7fb2a2d..08fffc4f0a 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -8,7 +8,7 @@ using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Compiler { /// /// Standard diff --git a/src/Tgstation.Server.Host/Components/DmbProvider.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs similarity index 97% rename from src/Tgstation.Server.Host/Components/DmbProvider.cs rename to src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs index d93b12ed78..3650c69ddc 100644 --- a/src/Tgstation.Server.Host/Components/DmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbProvider.cs @@ -2,7 +2,7 @@ using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Compiler { /// sealed class DmbProvider : IDmbProvider diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs similarity index 93% rename from src/Tgstation.Server.Host/Components/DreamMaker.cs rename to src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 7d1c19ba36..c6625bbaeb 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; @@ -9,11 +10,13 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Byond; +using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Compiler { /// sealed class DreamMaker : IDreamMaker @@ -47,9 +50,9 @@ namespace Tgstation.Server.Host.Components /// readonly IIOManager ioManager; /// - /// The for + /// The for /// - readonly IConfiguration configuration; + readonly StaticFiles.IConfiguration configuration; /// /// The for /// @@ -82,7 +85,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public DreamMaker(IByondManager byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger logger) + public DreamMaker(IByondManager byond, IIOManager ioManager, StaticFiles.IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger logger) { this.byond = byond; this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -286,8 +289,11 @@ namespace Tgstation.Server.Host.Components } if (!ddVerified) + { //server never validated or compile failed - await Task.WhenAll(CleanupFailedCompile(), eventConsumer.HandleEvent(EventType.CompileFailure, new List { job.ExitCode == 0 ? "1" : "0" }, cancellationToken)).ConfigureAwait(false); + await CleanupFailedCompile().ConfigureAwait(false); + await eventConsumer.HandleEvent(EventType.CompileFailure, new List { job.ExitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false); + } else { job.DMApiValidated = true; diff --git a/src/Tgstation.Server.Host/Components/Compiler/ICompileJobConsumer.cs b/src/Tgstation.Server.Host/Components/Compiler/ICompileJobConsumer.cs new file mode 100644 index 0000000000..398d6d038c --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Compiler/ICompileJobConsumer.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Hosting; +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Components.Compiler +{ + /// + /// Sink for s + /// + interface ICompileJobConsumer : IHostedService, IDisposable + { + /// + /// Load a new into the + /// + /// The to load + /// The for the operation + /// A representing the running operation + Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs similarity index 95% rename from src/Tgstation.Server.Host/Components/IDmbFactory.cs rename to src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs index bcdd48907a..e79f55b6f8 100644 --- a/src/Tgstation.Server.Host/Components/IDmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/IDmbFactory.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Compiler { /// /// Factory for s diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs similarity index 92% rename from src/Tgstation.Server.Host/Components/IDreamMaker.cs rename to src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs index d8610784cf..900a3e648e 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs @@ -1,8 +1,9 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Compiler { /// /// For managing the compiler diff --git a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs similarity index 95% rename from src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs rename to src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs index de3c7164ac..dcf5c0affe 100644 --- a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/TemporaryDmbProvider.cs @@ -1,7 +1,7 @@ using System; using Tgstation.Server.Host.Models; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Compiler { /// /// Temporary diff --git a/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs deleted file mode 100644 index 248590a01f..0000000000 --- a/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.Extensions.Hosting; -using System; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Host.Models; - -namespace Tgstation.Server.Host.Components -{ - interface ICompileJobConsumer : IHostedService, IDisposable - { - Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index f5d3db11dc..946a95b16a 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -1,7 +1,11 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; using System; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; namespace Tgstation.Server.Host.Components @@ -37,9 +41,9 @@ namespace Tgstation.Server.Host.Components IChat Chat { get; } /// - /// The for the + /// The for the /// - IConfiguration Configuration { get; } + StaticFiles.IConfiguration Configuration { get; } /// /// Get the associated with the diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 6ee66aceb6..2695689f01 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,9 +1,13 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; @@ -28,7 +32,7 @@ namespace Tgstation.Server.Host.Components public IChat Chat { get; } /// - public IConfiguration Configuration { get; } + public StaticFiles.IConfiguration Configuration { get; } /// /// The for the @@ -59,7 +63,20 @@ namespace Tgstation.Server.Host.Components /// CancellationTokenSource timerCts; - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory) + /// + /// Construct an + /// + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + /// The value of + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, StaticFiles.IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 527fdad14c..9bb2a2b73d 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -1,8 +1,11 @@ using Byond.TopicSender; using Microsoft.Extensions.Logging; using System; +using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; +using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; @@ -133,7 +136,7 @@ namespace Tgstation.Server.Host.Components try { var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger()); - var configuration = new Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); + var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); var chat = chatFactory.CreateChat(metadata.ChatSettings); try diff --git a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs index b72af7cf8e..a4135a29fa 100644 --- a/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs +++ b/src/Tgstation.Server.Host/Components/ReattachInfoHandler.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Compiler; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs similarity index 98% rename from src/Tgstation.Server.Host/Components/IRepository.cs rename to src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 08a500cb02..cb46b4a9d1 100644 --- a/src/Tgstation.Server.Host/Components/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Repository { /// /// Represents an on-disk git repository diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs similarity index 95% rename from src/Tgstation.Server.Host/Components/IRepositoryManager.cs rename to src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs index ac2823b33a..87544e2938 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Repository { /// /// Factory for creating and loading s diff --git a/src/Tgstation.Server.Host/Components/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs similarity index 99% rename from src/Tgstation.Server.Host/Components/Repository.cs rename to src/Tgstation.Server.Host/Components/Repository/Repository.cs index 218b21932c..4696bb66bb 100644 --- a/src/Tgstation.Server.Host/Components/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.IO; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Repository { /// sealed class Repository : IRepository diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs similarity index 98% rename from src/Tgstation.Server.Host/Components/RepositoryManager.cs rename to src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 118509530a..75c67c1e6f 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.IO; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Repository { /// sealed class RepositoryManager : IRepositoryManager diff --git a/src/Tgstation.Server.Host/Components/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs similarity index 99% rename from src/Tgstation.Server.Host/Components/Configuration.cs rename to src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 41f1f34977..d014ccd90d 100644 --- a/src/Tgstation.Server.Host/Components/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -11,7 +11,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.StaticFiles { /// sealed class Configuration : IConfiguration diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs similarity index 98% rename from src/Tgstation.Server.Host/Components/IConfiguration.cs rename to src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index e972f8e508..433e532413 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Security; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.StaticFiles { /// /// For managing the Configuration directory diff --git a/src/Tgstation.Server.Host/Components/ServerSideModifications.cs b/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs similarity index 94% rename from src/Tgstation.Server.Host/Components/ServerSideModifications.cs rename to src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs index e54faac733..7afb8ebf1f 100644 --- a/src/Tgstation.Server.Host/Components/ServerSideModifications.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs @@ -1,6 +1,4 @@ -using System; - -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.StaticFiles { /// /// Represents code modifications via configuration diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs index c422c33730..5ff46f692c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Executor.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Byond; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs b/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs index 84e2232bd3..127244af15 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IExecutor.cs @@ -1,4 +1,5 @@ using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Byond; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs index 9f307f44c0..35f423adc3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ISessionControllerFactory.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Byond; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs index 35326200d6..68717edc9c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdogFactory.cs @@ -1,4 +1,5 @@ using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Compiler; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs index 6ede61b0b6..271d840692 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ReattachInformation.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Components.Compiler; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs index e978b66003..29b7eb8844 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Session.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Session.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Byond; namespace Tgstation.Server.Host.Components.Watchdog { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index ec2afa72a0..1a24e7eb71 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 25e85688e0..4a5249e4e8 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Compiler; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index a3dca7bb30..2df5df1d9e 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using System; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Compiler; using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Watchdog diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs index 0dd94f8ba5..efe645c044 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogReattachInformation.cs @@ -1,4 +1,4 @@ -using System; +using Tgstation.Server.Host.Components.Compiler; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Watchdog diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index d00f4dabea..a29ac1dcf7 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Controller for managing the compiler /// - [Route("/" + nameof(DreamMaker))] + [Route("/" + nameof(Api.Models.DreamMaker))] public sealed class DreamMakerController : ModelController { /// diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 52b6b4364d..595b6cccb5 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -20,6 +20,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Watchdog; From 70e800ebcdad1d098eb84c30dcfc260eed2f257f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 18:55:23 -0400 Subject: [PATCH 355/397] Lift out event handlers. Static event handlers --- .../Components/EventConsumer.cs | 54 +++++++++++++ .../Components/IEventConsumer.cs | 4 +- .../Components/InstanceFactory.cs | 19 ++++- .../Components/StaticFiles/Configuration.cs | 32 +++++++- .../Components/StaticFiles/IConfiguration.cs | 2 +- .../Components/StaticFiles/IScriptExecutor.cs | 21 ++++++ .../Components/StaticFiles/ScriptExecutor.cs | 75 +++++++++++++++++++ .../Components/Watchdog/Watchdog.cs | 19 +++-- .../Components/Watchdog/WatchdogFactory.cs | 13 +++- 9 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/EventConsumer.cs create mode 100644 src/Tgstation.Server.Host/Components/StaticFiles/IScriptExecutor.cs create mode 100644 src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs diff --git a/src/Tgstation.Server.Host/Components/EventConsumer.cs b/src/Tgstation.Server.Host/Components/EventConsumer.cs new file mode 100644 index 0000000000..83e1c5ae69 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/EventConsumer.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Components.StaticFiles; +using Tgstation.Server.Host.Components.Watchdog; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class EventConsumer : IEventConsumer + { + /// + /// The for the + /// + readonly IConfiguration configuration; + + /// + /// The for the + /// + IWatchdog watchdog; + + /// + /// Construct an + /// + /// The value of + public EventConsumer(IConfiguration configuration) + { + this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + } + + /// + public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + { + if (watchdog == null) + throw new InvalidOperationException("EventConsumer used without watchdog set!"); + + if (!await configuration.HandleEvent(eventType, parameters, cancellationToken).ConfigureAwait(false)) + return false; + return await watchdog.HandleEvent(eventType, parameters, cancellationToken).ConfigureAwait(false); + } + + /// + /// Set the for the + /// + /// The value of + public void SetWatchdog(IWatchdog watchdog) + { + if (watchdog != null) + throw new InvalidOperationException("watchdog already set!"); + this.watchdog = watchdog; + } + } +} diff --git a/src/Tgstation.Server.Host/Components/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/IEventConsumer.cs index 986f9f2213..55945548c2 100644 --- a/src/Tgstation.Server.Host/Components/IEventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/IEventConsumer.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Components /// The /// The parameters for /// The for the operation - /// A representing the running operation - Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken); + /// A resulting in if more should run, otherwise + Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 9bb2a2b73d..f6c402c1fa 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -6,6 +6,7 @@ using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Compiler; using Tgstation.Server.Host.Components.Repository; +using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; @@ -81,6 +82,11 @@ namespace Tgstation.Server.Host.Components /// readonly IProviderFactory providerFactory; + /// + /// The for the + /// + readonly IScriptExecutor scriptExecutor; + /// /// Construct an /// @@ -97,7 +103,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -112,6 +119,7 @@ namespace Tgstation.Server.Host.Components this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller)); this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); + this.scriptExecutor = scriptExecutor ?? throw new ArgumentNullException(nameof(scriptExecutor)); } /// @@ -126,6 +134,9 @@ namespace Tgstation.Server.Host.Components var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); + var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, scriptExecutor, loggerFactory.CreateLogger()); + var eventConsumer = new EventConsumer(configuration); + var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, metadata.CloneMetadata()); try { @@ -136,18 +147,18 @@ namespace Tgstation.Server.Host.Components try { var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger()); - var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); var chat = chatFactory.CreateChat(metadata.ChatSettings); try { var sessionControllerFactory = new SessionControllerFactory(executor, byond, byondTopicSender, interopRegistrar, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata()); var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, metadata.CloneMetadata()); - var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, metadata.CloneMetadata()); + var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, metadata.CloneMetadata()); var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings); + eventConsumer.SetWatchdog(watchdog); try { - var dreamMaker = new DreamMaker(byond, ioManager, configuration, sessionControllerFactory, dmbFactory, application, watchdog, loggerFactory.CreateLogger()); + var dreamMaker = new DreamMaker(byond, ioManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, loggerFactory.CreateLogger()); return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory); } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index d014ccd90d..6442c22e7c 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; @@ -23,6 +24,12 @@ namespace Tgstation.Server.Host.Components.StaticFiles const string CodeModificationsHeadFile = "HeadInclude.dm"; const string CodeModificationsTailFile = "TailInclude.dm"; + static readonly IReadOnlyDictionary EventTypeScriptFileNameMap = new Dictionary + { + }; + + static readonly string SystemScriptFileExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".bat" : ".sh"; + /// /// The for /// @@ -38,6 +45,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// readonly ISymlinkFactory symlinkFactory; + /// + /// The for + /// + readonly IScriptExecutor scriptExecutor; + /// /// The for /// @@ -49,12 +61,14 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// The value of /// The value of /// The value of + /// The value of /// The value of - public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, ILogger logger) + public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IScriptExecutor scriptExecutor, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); + this.scriptExecutor = scriptExecutor ?? throw new ArgumentNullException(nameof(scriptExecutor)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -253,5 +267,21 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// public Task StopAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken); + + /// + public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + { + await EnsureDirectories(cancellationToken).ConfigureAwait(false); + + if (!EventTypeScriptFileNameMap.TryGetValue(eventType, out var scriptName)) + return true; + + var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, SystemScriptFileExtension, cancellationToken).ConfigureAwait(false); + var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory); + foreach (var I in files.Where(x => x.StartsWith(scriptName, StringComparison.Ordinal))) + if ((await scriptExecutor.ExecuteScript(ioManager.ConcatPath(resolvedScriptsDir, I), parameters, cancellationToken).ConfigureAwait(false)) != 0) + return false; + return true; + } } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index 433e532413..883568558e 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// /// For managing the Configuration directory /// - public interface IConfiguration : IHostedService + public interface IConfiguration : IHostedService, IEventConsumer { /// /// Copies all files in the CodeModifications directory to diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IScriptExecutor.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IScriptExecutor.cs new file mode 100644 index 0000000000..a9e26ff72f --- /dev/null +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IScriptExecutor.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Components.StaticFiles +{ + /// + /// For executing system shell scripts + /// + interface IScriptExecutor + { + /// + /// Execute a shell script and get the result + /// + /// The absolute path to the script + /// Command line parameters for the script + /// The for the operation + /// A resulting in the or if an error occurred + Task ExecuteScript(string scriptPath, IEnumerable parameters, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs b/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs new file mode 100644 index 0000000000..7aa7409d4c --- /dev/null +++ b/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs @@ -0,0 +1,75 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; + +namespace Tgstation.Server.Host.Components.StaticFiles +{ + /// + sealed class ScriptExecutor : IScriptExecutor + { + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Construct a + /// + /// The value of + /// The value of + public ScriptExecutor(IIOManager ioManager, ILogger logger) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + public async Task ExecuteScript(string scriptPath, IEnumerable parameters, CancellationToken cancellationToken) + { + try + { + using (var process = new Process()) + { + process.StartInfo.FileName = scriptPath; + process.StartInfo.WorkingDirectory = ioManager.GetDirectoryName(scriptPath); + process.StartInfo.Arguments = String.Join(" ", parameters); + process.EnableRaisingEvents = true; + + var tcs = new TaskCompletionSource(); + process.Exited += (a, b) => tcs.SetResult(null); + try + { + process.Start(); + using (cancellationToken.Register(() => tcs.SetCanceled())) + await tcs.Task.ConfigureAwait(false); + } + catch (InvalidOperationException) + { + try + { + process.Kill(); + process.WaitForExit(); + } + catch (InvalidOperationException) { } + } + + return process.ExitCode; + } + } + catch (Exception e) + { + logger.LogWarning("Error running shell script {0}! Exception: {1}", scriptPath, e); + return null; + } + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 4a5249e4e8..c27db4fed0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -76,6 +76,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IByondTopicSender byondTopicSender; + /// + /// The for the + /// + readonly IEventConsumer eventConsumer; + /// /// The for the /// @@ -134,7 +139,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The initial value of /// The value of /// The value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) + /// The value of + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -143,6 +149,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.autoStart = autoStart; @@ -775,13 +782,13 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) { string results; using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { if (!Running) - return; + return true; var builder = new StringBuilder(InteropConstants.DMTopicEvent); foreach (var I in parameters) @@ -795,7 +802,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } if (results == null) - return; + return true; List responses; try @@ -805,10 +812,12 @@ namespace Tgstation.Server.Host.Components.Watchdog catch { logger.LogInformation("Recieved invalid response from DD when parsing event {0}:{1}{2}", eventType, Environment.NewLine, results); - return; + return true; } await Task.WhenAll(responses.Select(x => chat.SendMessage(x.Message, x.ChannelIds, cancellationToken))).ConfigureAwait(false); + + return true; } /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 2df5df1d9e..2cf4f407af 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -46,11 +46,16 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IByondTopicSender byondTopicSender; + /// + /// The for the + /// + readonly IEventConsumer eventConsumer; + /// /// The for the /// readonly Api.Models.Instance instance; - + /// /// Construct a @@ -62,8 +67,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of + /// The value of /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, Api.Models.Instance instance) + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -72,10 +78,11 @@ namespace Tgstation.Server.Host.Components.Watchdog this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); } /// - public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, settings, instance, settings.AutoStart.Value); + public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, settings, instance, settings.AutoStart.Value); } } From 9a789ed76ca4de2d5705cce1a7fabee59ab1f530 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 18:56:13 -0400 Subject: [PATCH 356/397] Fix startup --- src/Tgstation.Server.Host/Core/Application.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 595b6cccb5..832fa0e5e3 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -23,6 +23,7 @@ using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; +using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Controllers; @@ -195,6 +196,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(new ByondTopicSender { From 515a1434db765d496f8d451c3496f36dd0f76a7a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 19:21:12 -0400 Subject: [PATCH 357/397] More repo stuff + serialize config script execution --- .../Components/Instance.cs | 7 +++-- .../Components/Repository/IRepository.cs | 15 +++++++++ .../Components/Repository/Repository.cs | 12 +++++++ .../Components/StaticFiles/Configuration.cs | 31 ++++++++++++++++--- .../Components/StaticFiles/IConfiguration.cs | 3 +- .../Components/StaticFiles/ScriptExecutor.cs | 4 ++- 6 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 2695689f01..9a22a5ea9a 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -94,10 +94,11 @@ namespace Tgstation.Server.Host.Components public void Dispose() { timerCts?.Dispose(); - Watchdog.Dispose(); - Chat.Dispose(); - RepositoryManager.Dispose(); compileJobConsumer.Dispose(); + Configuration.Dispose(); + Chat.Dispose(); + Watchdog.Dispose(); + RepositoryManager.Dispose(); } /// diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index cb46b4a9d1..d134a34639 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -65,6 +65,13 @@ namespace Tgstation.Server.Host.Components.Repository /// A resulting in the SHA of the new HEAD Task ResetToOrigin(CancellationToken cancellationToken); + /// + /// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository + /// + /// The for the operation + /// A resulting in the SHA of the new HEAD. if the merge resulted in conflict + Task MergeOrigin(CancellationToken cancellationToken); + /// /// Force push the current repository HEAD to ; /// @@ -73,6 +80,14 @@ namespace Tgstation.Server.Host.Components.Repository /// A representing the running operation Task PushHeadToTemporaryBranch(string accessString, CancellationToken cancellationToken); + /// + /// Runs the synchronize event script and attempts to push any changes made to the if on a tracked branch + /// + /// The access string to push to the origin repository + /// The for the operation + /// A representing the running operation + Task Sychronize(string accessString, CancellationToken cancellationToken); + /// /// Copies the current working directory to a given /// diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 4696bb66bb..52565c0a34 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -229,5 +229,17 @@ namespace Tgstation.Server.Host.Components.Repository throw new ArgumentNullException(nameof(path)); await ioMananger.CopyDirectory(".", path, new List { ".git" }, cancellationToken).ConfigureAwait(false); } + + /// + public Task MergeOrigin(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task Sychronize(string accessString, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 6442c22e7c..6ff0183354 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -9,6 +9,7 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; @@ -55,6 +56,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// readonly ILogger logger; + /// + /// The for + /// + readonly SemaphoreSlim semaphore; + /// /// Construct /// @@ -70,8 +76,18 @@ namespace Tgstation.Server.Host.Components.StaticFiles this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); this.scriptExecutor = scriptExecutor ?? throw new ArgumentNullException(nameof(scriptExecutor)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + semaphore = new SemaphoreSlim(1); } + /// + public void Dispose() => semaphore.Dispose(); + + /// + /// Ensures standard configuration directories exist + /// + /// The for the operation + /// A representing the running operation Task EnsureDirectories(CancellationToken cancellationToken) => Task.WhenAll(ioManager.CreateDirectory(CodeModificationsSubdirectory, cancellationToken), ioManager.CreateDirectory(EventScriptsSubdirectory, cancellationToken), ioManager.CreateDirectory(GameStaticFilesSubdirectory, cancellationToken)); /// @@ -276,11 +292,16 @@ namespace Tgstation.Server.Host.Components.StaticFiles if (!EventTypeScriptFileNameMap.TryGetValue(eventType, out var scriptName)) return true; - var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, SystemScriptFileExtension, cancellationToken).ConfigureAwait(false); - var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory); - foreach (var I in files.Where(x => x.StartsWith(scriptName, StringComparison.Ordinal))) - if ((await scriptExecutor.ExecuteScript(ioManager.ConcatPath(resolvedScriptsDir, I), parameters, cancellationToken).ConfigureAwait(false)) != 0) - return false; + //always execute in serial + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, SystemScriptFileExtension, cancellationToken).ConfigureAwait(false); + var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory); + + foreach (var I in files.Where(x => x.StartsWith(scriptName, StringComparison.Ordinal))) + if ((await scriptExecutor.ExecuteScript(ioManager.ConcatPath(resolvedScriptsDir, I), parameters, cancellationToken).ConfigureAwait(false)) != 0) + return false; + } return true; } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index 883568558e..3fdce2aa9b 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Hosting; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -10,7 +11,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// /// For managing the Configuration directory /// - public interface IConfiguration : IHostedService, IEventConsumer + public interface IConfiguration : IHostedService, IEventConsumer, IDisposable { /// /// Copies all files in the CodeModifications directory to diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs b/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs index 7aa7409d4c..14a2a97805 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/ScriptExecutor.cs @@ -35,13 +35,15 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// public async Task ExecuteScript(string scriptPath, IEnumerable parameters, CancellationToken cancellationToken) { + var joinedParams = String.Join(" ", parameters); + logger.LogInformation("Running script {0} {1}", scriptPath, joinedParams); try { using (var process = new Process()) { process.StartInfo.FileName = scriptPath; process.StartInfo.WorkingDirectory = ioManager.GetDirectoryName(scriptPath); - process.StartInfo.Arguments = String.Join(" ", parameters); + process.StartInfo.Arguments = joinedParams; process.EnableRaisingEvents = true; var tcs = new TaskCompletionSource(); From 8fbb6f3cf5c8fc5c48d8934958080bc5d94c76a2 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 20:36:54 -0400 Subject: [PATCH 358/397] Clean up password login to use SystemIdentifier as a key when logging in with system identites --- .../Controllers/HomeController.cs | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index c74858cf07..b45b0b38dd 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -76,54 +76,56 @@ namespace Tgstation.Server.Host.Controllers { if (ApiHeaders.IsTokenAuthentication) return BadRequest(new { message = "Cannot create a token using another token!" }); - - var user = await DatabaseContext.Users.Where(x => x.CanonicalName == ApiHeaders.Username.ToUpperInvariant()).Select(x => new User - { - Id = x.Id, - PasswordHash = x.PasswordHash, - SystemIdentifier = x.SystemIdentifier, - Enabled = x.Enabled - }).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); - if (user == null) - return Unauthorized(); - - ISystemIdentity identity = null; - if (user.PasswordHash != null) + ISystemIdentity identity; + try { - var originalHash = user.PasswordHash; - if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password)) - return Unauthorized(); - if (user.PasswordHash != originalHash) - { - var updatedUser = new User - { - Id = user.Id, - PasswordHash = originalHash - }; - DatabaseContext.Users.Attach(updatedUser); - updatedUser.PasswordHash = user.PasswordHash; - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - } + identity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false); } - else - try + catch (NotImplementedException) + { + identity = null; + } + using (identity) + { + IQueryable query; + if (identity == null) + query = DatabaseContext.Users.Where(x => x.CanonicalName == ApiHeaders.Username.ToUpperInvariant()); + else + query = DatabaseContext.Users.Where(x => x.SystemIdentifier == identity.Uid); + var user = await query.Select(x => new User { - identity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false); - if (identity == null || identity.Uid != user.SystemIdentifier) + Id = x.Id, + PasswordHash = x.PasswordHash, + Enabled = x.Enabled + }).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + if (user == null) + return Unauthorized(); + + if (identity == null) + { + var originalHash = user.PasswordHash; + if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password)) return Unauthorized(); + if (user.PasswordHash != originalHash) + { + var updatedUser = new User + { + Id = user.Id + }; + DatabaseContext.Users.Attach(updatedUser); + updatedUser.PasswordHash = user.PasswordHash; + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + } } - catch (NotImplementedException) - { - return StatusCode((int)HttpStatusCode.NotImplemented); - } - using (identity) { + if (!user.Enabled.Value) return Forbid(); var token = tokenFactory.CreateToken(user, out var expiry); if (identity != null) - identityCache.CacheSystemIdentity(user, identity, expiry.AddSeconds(10)); //expire the identity slightly after the auth token in case of lag + identityCache.CacheSystemIdentity(user, identity, expiry.AddSeconds(10)); //expire the identity slightly after the auth token in case of lag return Json(token); } } From e7323c85c3d4eed7b1a7cff43a4f335c322ef691 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 20:53:06 -0400 Subject: [PATCH 359/397] Clean up some repository stuff --- .../Models/Internal/RepositorySettings.cs | 14 ++ .../Rights/RepositoryRights.cs | 4 + .../Components/EventType.cs | 44 ++-- .../Components/Repository/IRepository.cs | 8 +- .../Components/Repository/Repository.cs | 237 +++++++++++++----- 5 files changed, 220 insertions(+), 87 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index c5ace7499c..14dac247ab 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -48,5 +48,19 @@ namespace Tgstation.Server.Api.Models.Internal [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] [Required] public bool? ShowTestMergeCommitters { get; set; } + + /// + /// If test merge commits should be kept when auto updating. May cause merge conflicts which will block the update + /// + [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] + [Required] + public bool? AutoUpdatesKeepTestMerges { get; set; } + + /// + /// If synchronization should occur when auto updating + /// + [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] + [Required] + public bool? AutoUpdatesSynchronize { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index 23703fd446..faf745fa79 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -48,5 +48,9 @@ namespace Tgstation.Server.Api.Rights /// User may read all fields in the with the exception of /// Read = 512, + /// + /// User may change and + /// + ChangeAutoUpdateSettings = 1024 } } diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs index 615c7ed3bb..e9442b0d10 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -10,7 +10,7 @@ /// RepoResetOrigin = 0, /// - /// Parameters: Reference name, commit sha + /// Parameters: Checkout target /// RepoCheckout = 1, /// @@ -18,18 +18,18 @@ /// RepoFetch = 2, /// - /// Parameters: Comma separated list in form of "#{Pull Request Number} @ {7 character SHA} + /// Parameters: Pull request number, pull request sha, merger name, merger message /// - RepoMergePullRequests = 3, + RepoMergePullRequest = 3, + /// + /// Parameters: Absolute path to repository root, committer name, committer email + /// + RepoPreSynchronize = 4, /// /// Parameters: Current version, new version /// - ByondChangeStart = 4, - /// - /// No parameters - /// - ByondChangeCancelled = 5, + ByondChangeStart = 5, /// /// Parameters: Error string /// @@ -37,36 +37,42 @@ /// /// No parameters /// - ByondStageComplete = 7, - /// - /// No parameters - /// - ByondChangeComplete = 8, + ByondChangeComplete = 7, /// /// Parameters: Origin commit sha /// - CompileStart = 9, + CompileStart = 8, /// /// No parameters /// - CompileCancelled = 10, + CompileCancelled = 9, /// /// Parameters: "1" if compile succeeded and api validation failed, "0" otherwise /// - CompileFailure = 11, + CompileFailure = 10, /// /// No parameters /// - CompileComplete = 12, + CompileComplete = 11, /// /// Parameters: Exit code /// - DDOtherCrash = 13, + DDOtherCrash = 12, /// /// No parameters /// - DDOtherExit = 14, + DDOtherExit = 13, + + /// + /// No parameters + /// + InstanceAutoUpdateStart = 14, + + /// + /// Parameters: Base sha, target sha, base reference, target reference + /// + RepoMergeConflict = 15, } } diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index d134a34639..10412a6458 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -44,11 +44,11 @@ namespace Tgstation.Server.Host.Components.Repository /// The commit in the pull request to merge /// The name of the merge committer /// The e-mail of the merge committer - /// The body of the commit message /// The access string to fetch from the origin repository /// The for the operation + /// A string to identify the user that merged /// A resulting in the SHA of the new HEAD on success, on merge conflict - Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitBody, string accessString, CancellationToken cancellationToken); + Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, string mergerIdentifier, CancellationToken cancellationToken); /// /// Fetch commits from the origin repository @@ -68,9 +68,11 @@ namespace Tgstation.Server.Host.Components.Repository /// /// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository /// + /// The name of the merge committer + /// The e-mail of the merge committer /// The for the operation /// A resulting in the SHA of the new HEAD. if the merge resulted in conflict - Task MergeOrigin(CancellationToken cancellationToken); + Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken); /// /// Force push the current repository HEAD to ; diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 52565c0a34..3e26156303 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -12,6 +12,8 @@ namespace Tgstation.Server.Host.Components.Repository /// sealed class Repository : IRepository { + const string UnknownReference = ""; + /// /// The branch name used for publishing testmerge commits /// @@ -39,6 +41,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly IIOManager ioMananger; + /// + /// The for the + /// + readonly IEventConsumer eventConsumer; + /// /// to be taken when is called /// @@ -49,11 +56,13 @@ namespace Tgstation.Server.Host.Components.Repository /// /// The value of /// The value of + /// The value of /// The value if - public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, Action onDispose) + public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, Action onDispose) { this.repository = repository ?? throw new ArgumentNullException(nameof(repository)); this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); IsGitHubRepository = Origin.ToUpperInvariant().Contains("://GITHUB.COM/"); } @@ -97,79 +106,104 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitBody, string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public async Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, string mergerIdentifier, CancellationToken cancellationToken) { + + if (!IsGitHubRepository) + throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!"); + var Refspec = new List(); var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", pullRequestNumber); var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", pullRequestNumber, prBranchName); Refspec.Add(String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", pullRequestNumber, prBranchName)); var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", pullRequestNumber); - var remote = repository.Network.Remotes.Add("temp_pr_fetch", GenerateAuthUrl(Origin, accessString)); - try - { - cancellationToken.ThrowIfCancellationRequested(); - Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions - { - Prune = true, - OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, - OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested - }, logMessage); - } - catch (UserCancelledException) { } - finally - { - repository.Network.Remotes.Remove(remote.Name); - //commit is there and we never gc so - repository.Branches.Remove(localBranchName); - repository.Branches.Remove(prBranchName); - } - - cancellationToken.ThrowIfCancellationRequested(); - var originalCommit = repository.Head; - var result = repository.Merge(targetCommit, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions + MergeResult result = null; + await Task.Factory.StartNew(() => { - CommitOnSuccess = true, - FailOnConflict = true, - FastForwardStrategy = FastForwardStrategy.NoFastForward, - SkipReuc = true, - }); + var remote = repository.Network.Remotes.Add("temp_pr_fetch", GenerateAuthUrl(Origin, accessString)); + try + { + cancellationToken.ThrowIfCancellationRequested(); + Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions + { + Prune = true, + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested + }, logMessage); + } + catch (UserCancelledException) { } + finally + { + repository.Network.Remotes.Remove(remote.Name); + //commit is there and we never gc so + repository.Branches.Remove(localBranchName); + repository.Branches.Remove(prBranchName); + } - if (result.Status != MergeStatus.NonFastForward) + cancellationToken.ThrowIfCancellationRequested(); + + result = repository.Merge(targetCommit, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions + { + CommitOnSuccess = true, + FailOnConflict = true, + FastForwardStrategy = FastForwardStrategy.NoFastForward, + SkipReuc = true + }); + + cancellationToken.ThrowIfCancellationRequested(); + + if (result.Status == MergeStatus.Conflicts) + { + RawCheckout(originalCommit.CanonicalName ?? originalCommit.Tip.Sha); + cancellationToken.ThrowIfCancellationRequested(); + } + + repository.RemoveUntrackedFiles(); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + + if (result.Status == MergeStatus.Conflicts) { - RawCheckout(originalCommit.FriendlyName ?? originalCommit.Tip.Sha); + await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { originalCommit.Tip.Sha, targetCommit, originalCommit.FriendlyName ?? UnknownReference, prBranchName }, cancellationToken).ConfigureAwait(false); return null; } return result.Commit.Sha; - - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + } /// - public Task CheckoutObject(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() => RawCheckout(committish), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); - - /// - public Task FetchOrigin(string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public async Task CheckoutObject(string committish, CancellationToken cancellationToken) { - var remote = repository.Network.Remotes.First(); - try + if (committish == null) + throw new ArgumentNullException(nameof(committish)); + await eventConsumer.HandleEvent(EventType.RepoCheckout, new List { committish }, cancellationToken).ConfigureAwait(false); + await Task.Factory.StartNew(() => RawCheckout(committish), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + } + + /// + public Task FetchOrigin(string accessString, CancellationToken cancellationToken) => Task.WhenAll( + eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty(), cancellationToken), + Task.Factory.StartNew(() => { - Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions + var remote = repository.Network.Remotes.First(); + try { - Prune = true, - OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, - OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested - }, "Fetch origin commits"); - } - catch (UserCancelledException) - { - cancellationToken.ThrowIfCancellationRequested(); - } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions + { + Prune = true, + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested + }, "Fetch origin commits"); + } + catch (UserCancelledException) + { + cancellationToken.ThrowIfCancellationRequested(); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current)); /// public Task PushHeadToTemporaryBranch(string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -205,22 +239,26 @@ namespace Tgstation.Server.Host.Components.Repository { repository.Branches.Remove(branch); } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// - public Task ResetToOrigin(CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public async Task ResetToOrigin(CancellationToken cancellationToken) { if (!repository.Head.IsTracking) throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!"); var trackedBranch = repository.Head.TrackedBranch; - Commands.Checkout((LibGit2Sharp.Repository)repository, repository.Head.TrackedBranch, new CheckoutOptions + await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false); + return await Task.Factory.StartNew(() => { - CheckoutModifiers = CheckoutModifiers.Force - }); - repository.RemoveUntrackedFiles(); - return trackedBranch.Tip.Sha; - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + Commands.Checkout((LibGit2Sharp.Repository)repository, repository.Head.TrackedBranch, new CheckoutOptions + { + CheckoutModifiers = CheckoutModifiers.Force + }); + cancellationToken.ThrowIfCancellationRequested(); + repository.RemoveUntrackedFiles(); + return trackedBranch.Tip.Sha; + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + } /// public async Task CopyTo(string path, CancellationToken cancellationToken) @@ -231,15 +269,84 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public Task MergeOrigin(CancellationToken cancellationToken) + public async Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken) { - throw new NotImplementedException(); + MergeResult result = null; + Branch trackedBranch = null; + + var oldHead = repository.Head; + + await Task.Factory.StartNew(() => + { + if (!repository.Head.IsTracking) + throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!"); + trackedBranch = repository.Head.TrackedBranch; + + result = repository.Merge(trackedBranch, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions + { + CommitOnSuccess = true, + FailOnConflict = true, + FastForwardStrategy = FastForwardStrategy.Default, + SkipReuc = true, + }); + + cancellationToken.ThrowIfCancellationRequested(); + + if (result.Status == MergeStatus.Conflicts) + { + RawCheckout(oldHead.CanonicalName); + cancellationToken.ThrowIfCancellationRequested(); + } + + repository.RemoveUntrackedFiles(); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + + if (result.Status == MergeStatus.Conflicts) + { + await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { oldHead.Tip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken).ConfigureAwait(false); + return null; + } + + return Head; } /// - public Task Sychronize(string accessString, CancellationToken cancellationToken) + public async Task Sychronize(string accessString, CancellationToken cancellationToken) { - throw new NotImplementedException(); + var startHead = Head; + + if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) + return; + + if (Head == startHead || !repository.Head.IsTracking) + return; + + await Task.Factory.StartNew(() => + { + cancellationToken.ThrowIfCancellationRequested(); + var remote = repository.Network.Remotes.Add("temp_push", GenerateAuthUrl(Origin, accessString)); + try + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + repository.Network.Push(repository.Head, new PushOptions + { + OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested, + OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested, + OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested + }); + } + catch (UserCancelledException) + { + cancellationToken.ThrowIfCancellationRequested(); + } + } + finally + { + repository.Network.Remotes.Remove(remote.Name); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); } } } From 4418fc0964c3f1ac09a5a586c5ee754982a389bd Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 21:00:51 -0400 Subject: [PATCH 360/397] Skeleton RepositoryController --- .../Controllers/RepositoryController.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/Tgstation.Server.Host/Controllers/RepositoryController.cs diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs new file mode 100644 index 0000000000..e662494c48 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// Controller for managing the s + /// + [Route("/" + nameof(Api.Models.Repository))] + public sealed class RepositoryController : ModelController + { + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) + { + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + } +} From 0a585c8cb4051aa5f858f6532240e71f64830a5d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 22:02:04 -0400 Subject: [PATCH 361/397] Repo controller create --- .../Models/Internal/RepositorySettings.cs | 12 +++ src/Tgstation.Server.Api/Models/Repository.cs | 8 +- .../Models/RevisionInformation.cs | 7 +- .../Components/Instance.cs | 40 +++++++-- .../Components/InstanceFactory.cs | 4 +- .../Repository/IRepositoryManager.cs | 14 ++- .../Repository/RepositoryManager.cs | 69 ++++++++++----- .../Controllers/DreamMakerController.cs | 5 ++ .../Controllers/RepositoryController.cs | 87 ++++++++++++++++++- .../Models/RepositorySettings.cs | 17 +++- .../Models/RevisionInformation.cs | 3 +- 11 files changed, 218 insertions(+), 48 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index 14dac247ab..4afc3d8394 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -9,6 +9,18 @@ namespace Tgstation.Server.Api.Models.Internal [Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)] public class RepositorySettings { + /// + /// The origin URL. If , the does not exist + /// + [Permissions(WriteRight = RepositoryRights.SetOrigin)] + public string Origin { get; set; } + + /// + /// The last commit recognized from + /// + [Permissions(DenyWrite = true)] + public string LastOriginCommitSha { get; set; } + /// /// The name of the committer /// diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index db3ed5b303..13a4b48814 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -8,17 +8,11 @@ namespace Tgstation.Server.Api.Models /// public sealed class Repository : Internal.RepositorySettings { - /// - /// The origin URL. If , the does not exist - /// - [Permissions(WriteRight = RepositoryRights.SetOrigin)] - public string Origin { get; set; } - /// /// The commit HEAD points to /// [Permissions(WriteRight = RepositoryRights.SetSha)] - public string NewRevision { get; set; } + public string Sha { get; set; } /// /// The current for the diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs index 0c769f02e7..9a3de97f05 100644 --- a/src/Tgstation.Server.Api/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs @@ -4,10 +4,15 @@ namespace Tgstation.Server.Api.Models { /// public sealed class RevisionInformation : Internal.RevisionInformation - { + { /// /// The s active in the /// public List TestMerges { get; set; } + + /// + /// The s made from the + /// + public List CompileJobs { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 9a22a5ea9a..6d16695529 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading; @@ -33,7 +34,7 @@ namespace Tgstation.Server.Host.Components /// public StaticFiles.IConfiguration Configuration { get; } - + /// /// The for the /// @@ -49,6 +50,11 @@ namespace Tgstation.Server.Host.Components /// readonly IDmbFactory dmbFactory; + /// + /// The for the + /// + readonly ILogger logger; + /// /// The for the /// @@ -76,7 +82,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, StaticFiles.IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory) + /// The value of + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, StaticFiles.IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, ILogger logger) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); @@ -88,6 +95,7 @@ namespace Tgstation.Server.Host.Components this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// @@ -109,9 +117,8 @@ namespace Tgstation.Server.Host.Components /// A representing the running operation async Task TimerLoop(int minutes, CancellationToken cancellationToken) { - try - { - while (true) + while (true) + try { await Task.Delay(new TimeSpan(0, minutes, 0), cancellationToken).ConfigureAwait(false); @@ -128,14 +135,31 @@ namespace Tgstation.Server.Host.Components }); using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { - await dbTask.ConfigureAwait(false); + try + { + await dbTask.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + logger.LogWarning("Database error in auto update loop! Exception: {0}", e); + continue; + } + if (repo == null) + continue; await repo.FetchOrigin(accessToken, cancellationToken).ConfigureAwait(false); await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); var job = await DreamMaker.Compile(projectName, timeout, repo, cancellationToken).ConfigureAwait(false); } } - } - catch (OperationCanceledException) { } + catch (OperationCanceledException) { } + catch (Exception e) + { + logger.LogError("Error in auto update loop! Exception: {0}", e); + } } /// diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index f6c402c1fa..9646712c84 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -143,7 +143,7 @@ namespace Tgstation.Server.Host.Components var commandFactory = new CommandFactory(application); var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory, providerFactory); - var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); + var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer); try { var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger()); @@ -160,7 +160,7 @@ namespace Tgstation.Server.Host.Components { var dreamMaker = new DreamMaker(byond, ioManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, loggerFactory.CreateLogger()); - return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory); + return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory, loggerFactory.CreateLogger()); } catch { diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs index 87544e2938..92b5ad0edd 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs @@ -13,16 +13,24 @@ namespace Tgstation.Server.Host.Components.Repository /// Attempt to load the from the default location /// /// The for the operation - /// The loaded + /// The loaded if it exists, otherwise Task LoadRepository(CancellationToken cancellationToken); /// - /// Delete the current and replaces it with a clone of the repository at + /// Clone the repository at /// /// The of the remote repository to clone + /// The branch to clone /// The access string to clone from /// The for the operation /// The newly cloned - Task CloneRepository(Uri url, string accessString, CancellationToken cancellationToken); + Task CloneRepository(Uri url, string initialBranch, string accessString, CancellationToken cancellationToken); + + /// + /// Delete the current repository + /// + /// The for the operation + /// A representing the running operation + Task DeleteRepository(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 75c67c1e6f..ecaf3c5d98 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Repository @@ -15,6 +16,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly IEventConsumer eventConsumer; + /// /// The for the /// @@ -30,10 +36,12 @@ namespace Tgstation.Server.Host.Components.Repository /// /// The value of /// The value of - public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager) + /// The value of + public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer) { this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); semaphore = new SemaphoreSlim(1); } @@ -41,28 +49,32 @@ namespace Tgstation.Server.Host.Components.Repository public void Dispose() => semaphore.Dispose(); /// - public async Task CloneRepository(Uri url, string accessString, CancellationToken cancellationToken) + public async Task CloneRepository(Uri url, string initialBranch, string accessString, CancellationToken cancellationToken) { - await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false); - - await Task.Factory.StartNew(() => - { - string path = null; - try + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false)) { - path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url.ToString(), accessString), ioManager.ResolvePath("."), new CloneOptions - { - OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, - RecurseSubmodules = true, - OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, - RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested - }); - } - catch (UserCancelledException) { } - cancellationToken.ThrowIfCancellationRequested(); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + await DeleteRepository(cancellationToken).ConfigureAwait(false); + await Task.Factory.StartNew(() => + { + string path = null; + try + { + path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url.ToString(), accessString), ioManager.ResolvePath("."), new CloneOptions + { + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + RecurseSubmodules = true, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, + RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested, + BranchName = initialBranch + }); + } + catch (UserCancelledException) { } + cancellationToken.ThrowIfCancellationRequested(); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + } return await LoadRepository(cancellationToken).ConfigureAwait(false); } @@ -73,14 +85,27 @@ namespace Tgstation.Server.Host.Components.Repository LibGit2Sharp.Repository repo = null; await Task.Factory.StartNew(() => { - repo = new LibGit2Sharp.Repository(ioManager.ResolvePath(".")); + try + { + repo = new LibGit2Sharp.Repository(ioManager.ResolvePath(".")); + } + catch (RepositoryNotFoundException) { } }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + if (repo == null) + return null; var localSemaphore = semaphore; - return new Repository(repo, ioManager, () => + return new Repository(repo, ioManager, eventConsumer, () => { localSemaphore?.Release(); localSemaphore = null; }); } + + /// + public async Task DeleteRepository(CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index a29ac1dcf7..d25f914517 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -129,6 +129,11 @@ namespace Tgstation.Server.Host.Controllers Task revInfoTask; using (var repo = await instance.RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { + if (repo == null) + { + job.ExceptionDetails = "Missing repository!"; + return; + } revInfoTask = databaseContext.RevisionInformations.Where(x => x.CommitSha == repo.Head).Select(x => new RevisionInformation { Id = x.Id }).FirstAsync(); compileJob = await instance.DreamMaker.Compile(projectName, timeout.Value, repo, cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index e662494c48..1c126a276b 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -1,5 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -9,8 +17,8 @@ namespace Tgstation.Server.Host.Controllers /// /// Controller for managing the s /// - [Route("/" + nameof(Api.Models.Repository))] - public sealed class RepositoryController : ModelController + [Route("/" + nameof(Repository))] + public sealed class RepositoryController : ModelController { /// /// The for the @@ -22,10 +30,83 @@ namespace Tgstation.Server.Host.Controllers /// /// The for the /// The for the - /// The value of + /// The value of public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); } + + static string GetAccessString(Api.Models.Internal.RepositorySettings repositorySettings) => repositorySettings.AccessUser != null ? String.Concat(repositorySettings.AccessUser, '@', repositorySettings.AccessToken) : null; + + async Task PopulateApi(Repository model, Components.Repository.IRepository repository, string lastOriginCommitSha, CancellationToken cancellationToken) + { + model.IsGitHub = repository.IsGitHubRepository; + model.Origin = repository.Origin; + model.Reference = repository.Reference; + model.Sha = repository.Head; + + //rev info stuff + var revisionInfo = await DatabaseContext.RevisionInformations.Where(x => x.CommitSha == model.Sha) + .Include(x => x.CompileJobs) + .Include(x => x.TestMerges) //minimal info, they can query the rest if they're allowed + .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); //search every rev info because LOL SHA COLLISIONS + + var needsDbUpdate = revisionInfo == default; + if (needsDbUpdate) + { + //needs insertion + revisionInfo = new Models.RevisionInformation + { + CommitSha = model.Sha, + CompileJobs = new List(), + TestMerges = new List(), //non null vals for api returns + OriginCommitSha = lastOriginCommitSha ?? model.Sha + }; + + DatabaseContext.RevisionInformations.Add(revisionInfo); + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + } + + model.RevisionInformation = revisionInfo.ToApi(); + return needsDbUpdate; + } + + /// + [TgsAuthorize(RepositoryRights.SetOrigin)] + public override async Task Create([FromBody] Repository model, CancellationToken cancellationToken) + { + if (model == null) + return BadRequest(new { message = "Missing request model!" }); + + if (model.Origin == null) + return BadRequest(new { message = "Missing repo origin!" }); + + if (model.AccessUser == null ^ model.AccessToken == null) + return BadRequest(new { message = "Either both accessToken and accessUser must be present or neither!" }); + + var currentModel = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.RepositorySettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + if (currentModel == default) + return StatusCode((int)HttpStatusCode.Gone); + + + currentModel.AccessToken = model.AccessToken; + currentModel.AccessUser = model.AccessUser; + currentModel.Origin = model.Origin; //intentionally only these fields, user not allowed to change anything else atm + var cloneBranch = model.Reference; + + var repoManager = instanceManager.GetInstance(Instance).RepositoryManager; + using (var repo = await repoManager.CloneRepository(new Uri(currentModel.Origin), cloneBranch, GetAccessString(currentModel), cancellationToken).ConfigureAwait(false)) + { + if (repo == null) + //clone conflict + return Conflict(); + var api = currentModel.ToApi(); + await PopulateApi(api, repo, null, cancellationToken).ConfigureAwait(false); + currentModel.LastOriginCommitSha = repo.Head; + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + return Json(api); + } + } } } diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 3d42baf883..2a9eef753e 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class RepositorySettings : Api.Models.Internal.RepositorySettings + public sealed class RepositorySettings : Api.Models.Internal.RepositorySettings, IApiConvertable { /// /// The row Id @@ -20,5 +21,19 @@ namespace Tgstation.Server.Host.Models /// [Required] public Instance Instance { get; set; } + + /// + public Repository ToApi() => new Repository { + AccessToken = AccessToken, + AccessUser = AccessUser, + AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges, + AutoUpdatesSynchronize = AutoUpdatesSynchronize, + CommitterEmail = CommitterEmail, + CommitterName = CommitterName, + //intentionally don't populate origin just in case + PushTestMergeCommits = PushTestMergeCommits, + //revision information and the rest retrieved by controller + ShowTestMergeCommitters = ShowTestMergeCommitters + }; } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 69960d6356..23db58d0f8 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -31,7 +31,8 @@ namespace Tgstation.Server.Host.Models { CommitSha = CommitSha, OriginCommitSha = OriginCommitSha, - TestMerges = TestMerges.Select(x => x.ToApi()).ToList() + TestMerges = TestMerges.Select(x => x.ToApi()).ToList(), + CompileJobs = CompileJobs.Select(x => x.ToApi()).ToList() }; } } From b739d8088d51daf316705a2f09cc83f14b908ec7 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 22:07:00 -0400 Subject: [PATCH 362/397] RepositoryController Delete --- src/Tgstation.Server.Api/Rights/RepositoryRights.cs | 6 +++++- .../Controllers/RepositoryController.cs | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index faf745fa79..fa86528d28 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -51,6 +51,10 @@ namespace Tgstation.Server.Api.Rights /// /// User may change and /// - ChangeAutoUpdateSettings = 1024 + ChangeAutoUpdateSettings = 1024, + /// + /// User may delete the + /// + Delete = 2048 } } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 1c126a276b..fa06d354f4 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -108,5 +108,17 @@ namespace Tgstation.Server.Host.Controllers return Json(api); } } + + /// + /// Delete the + /// + /// The for the operation + /// A resulting in the of the operation + [TgsAuthorize(RepositoryRights.Delete)] + public async Task Delete(CancellationToken cancellationToken) + { + await instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken).ConfigureAwait(false); + return Ok(); + } } } From 75373ed2e954ffb139bbc7d42b80a73421f33765 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 22:22:08 -0400 Subject: [PATCH 363/397] RepositoryController Read --- .../Controllers/RepositoryController.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index fa06d354f4..1b19f662cb 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -84,7 +84,7 @@ namespace Tgstation.Server.Host.Controllers if (model.AccessUser == null ^ model.AccessToken == null) return BadRequest(new { message = "Either both accessToken and accessUser must be present or neither!" }); - var currentModel = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.RepositorySettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (currentModel == default) return StatusCode((int)HttpStatusCode.Gone); @@ -117,8 +117,39 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize(RepositoryRights.Delete)] public async Task Delete(CancellationToken cancellationToken) { + var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + if (currentModel == default) + return StatusCode((int)HttpStatusCode.Gone); + + currentModel.Origin = null; + currentModel.LastOriginCommitSha = null; + currentModel.AccessToken = null; + currentModel.AccessUser = null; + + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + await instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken).ConfigureAwait(false); return Ok(); } + + /// + [TgsAuthorize(RepositoryRights.Read)] + public override async Task Read(CancellationToken cancellationToken) + { + var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + if (currentModel == default) + return StatusCode((int)HttpStatusCode.Gone); + + var api = currentModel.ToApi(); + + using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, cancellationToken).ConfigureAwait(false)) + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + return Json(api); + } + } } } From cdc891904e4c1173e98643165cc7b61a8e0803fa Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 01:19:55 -0400 Subject: [PATCH 364/397] Oof there's the Update of RepositoryController for ya --- src/Tgstation.Server.Api/Models/Repository.cs | 8 +- .../Models/RevisionInformation.cs | 7 +- .../Components/Repository/IRepository.cs | 18 +- .../Components/Repository/Repository.cs | 30 ++- .../Watchdog/SessionControllerFactory.cs | 6 +- .../Controllers/RepositoryController.cs | 211 ++++++++++++++++-- .../Models/DatabaseContext.cs | 10 +- .../Models/RepositorySettings.cs | 2 +- .../Models/RevInfoTestMerge.cs | 27 +++ .../Models/RevisionInformation.cs | 12 +- src/Tgstation.Server.Host/Models/TestMerge.cs | 18 +- .../Security/WindowsSystemIdentityFactory.cs | 2 +- 12 files changed, 315 insertions(+), 36 deletions(-) create mode 100644 src/Tgstation.Server.Host/Models/RevInfoTestMerge.cs diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 13a4b48814..004be123cf 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -24,7 +24,13 @@ namespace Tgstation.Server.Api.Models /// If the repository was cloned from GitHub.com. If this enables test merge functionality /// [Permissions(DenyWrite = true)] - public bool IsGitHub { get; set; } + public bool? IsGitHub { get; set; } + + /// + /// Do the equivalent of a git pull. Will attempt to merge unless is also specified + /// + [Permissions(WriteRight = RepositoryRights.UpdateBranch)] + public bool? UpdateReference { get; set; } /// /// The branch or tag HEAD points to diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs index 9a3de97f05..0340478d97 100644 --- a/src/Tgstation.Server.Api/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs @@ -5,10 +5,15 @@ namespace Tgstation.Server.Api.Models /// public sealed class RevisionInformation : Internal.RevisionInformation { + /// + /// The that was created with this + /// + public TestMerge PrimaryTestMerge { get; set; } + /// /// The s active in the /// - public List TestMerges { get; set; } + public List ActiveTestMerges { get; set; } /// /// The s made from the diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 10412a6458..23b6d68b47 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -14,6 +14,21 @@ namespace Tgstation.Server.Host.Components.Repository /// bool IsGitHubRepository { get; } + /// + /// The if this + /// + string GitHubOwner { get; } + + /// + /// The if this + /// + string GitHubRepoName { get; } + + /// + /// If tracks an upstream branch + /// + bool Tracking { get; } + /// /// The SHA of the HEAD /// @@ -46,9 +61,8 @@ namespace Tgstation.Server.Host.Components.Repository /// The e-mail of the merge committer /// The access string to fetch from the origin repository /// The for the operation - /// A string to identify the user that merged /// A resulting in the SHA of the new HEAD on success, on merge conflict - Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, string mergerIdentifier, CancellationToken cancellationToken); + Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, CancellationToken cancellationToken); /// /// Fetch commits from the origin repository diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 3e26156303..13fdf97475 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -22,6 +22,15 @@ namespace Tgstation.Server.Host.Components.Repository /// public bool IsGitHubRepository { get; } + /// + public string GitHubOwner { get; } + + /// + public string GitHubRepoName { get; } + + /// + public bool Tracking => repository.Head.IsTracking; + /// public string Head => repository.Head.Tip.Sha; @@ -51,6 +60,19 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly Action onDispose; + static void GetRepositoryOwnerName(string remote, out string owner, out string name) + { + //Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1] + //Yes use .git twice in case it was weird + var toRemove = new string[] { ".git", "/", ".git" }; + foreach (string item in toRemove) + if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase)) + remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase)); + var splits = remote.Split('/'); + name = splits[splits.Length - 1]; + owner = splits[splits.Length - 2].Split('.')[0]; + } + /// /// Construct a /// @@ -65,6 +87,12 @@ namespace Tgstation.Server.Host.Components.Repository this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); IsGitHubRepository = Origin.ToUpperInvariant().Contains("://GITHUB.COM/"); + if (IsGitHubRepository) + { + GetRepositoryOwnerName(Origin, out var owner, out var name); + GitHubOwner = owner; + GitHubRepoName = name; + } } /// @@ -106,7 +134,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, string mergerIdentifier, CancellationToken cancellationToken) + public async Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, CancellationToken cancellationToken) { if (!IsGitHubRepository) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 1a24e7eb71..1afc9a1571 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -116,14 +116,14 @@ namespace Tgstation.Server.Host.Components.Watchdog InstanceName = instance.Name, Revision = dmbProvider.CompileJob.RevisionInformation }; - interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.TestMerges.Select(x => new TestMerge + interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => new TestMerge { Author = x.Author, Body = x.BodyAtMerge, Comment = x.Comment, - CommitSha = x.RevisionInformation.CommitSha, + CommitSha = x.PrimaryRevisionInformation.CommitSha, Number = x.Number, - OriginCommitSha = x.RevisionInformation.OriginCommitSha, + OriginCommitSha = x.PrimaryRevisionInformation.OriginCommitSha, PullRequestCommit = x.PullRequestRevision, TimeMerged = x.MergedAt.Ticks, Title = x.TitleAtMerge, diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 1b19f662cb..bded31639d 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -3,7 +3,9 @@ using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Net; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -15,7 +17,7 @@ using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Controllers { /// - /// Controller for managing the s + /// Controller for managing the s /// [Route("/" + nameof(Repository))] public sealed class RepositoryController : ModelController @@ -25,30 +27,32 @@ namespace Tgstation.Server.Host.Controllers /// readonly IInstanceManager instanceManager; + /// + /// The for the + /// + readonly Octokit.IGitHubClient gitHubClient; + /// /// Construct a /// /// The for the /// The for the /// The value of - public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) + /// The value of + public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, Octokit.IGitHubClient gitHubClient) : base(databaseContext, authenticationContextFactory, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); } static string GetAccessString(Api.Models.Internal.RepositorySettings repositorySettings) => repositorySettings.AccessUser != null ? String.Concat(repositorySettings.AccessUser, '@', repositorySettings.AccessToken) : null; - async Task PopulateApi(Repository model, Components.Repository.IRepository repository, string lastOriginCommitSha, CancellationToken cancellationToken) + async Task LoadRevisionInformation(Components.Repository.IRepository repository, Action revInfoSink, CancellationToken cancellationToken) { - model.IsGitHub = repository.IsGitHubRepository; - model.Origin = repository.Origin; - model.Reference = repository.Reference; - model.Sha = repository.Head; - - //rev info stuff - var revisionInfo = await DatabaseContext.RevisionInformations.Where(x => x.CommitSha == model.Sha) + var repoSha = repository.Head; + var revisionInfo = await DatabaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha) .Include(x => x.CompileJobs) - .Include(x => x.TestMerges) //minimal info, they can query the rest if they're allowed + .Include(x => x.ActiveTestMerges) //minimal info, they can query the rest if they're allowed .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); //search every rev info because LOL SHA COLLISIONS var needsDbUpdate = revisionInfo == default; @@ -57,16 +61,30 @@ namespace Tgstation.Server.Host.Controllers //needs insertion revisionInfo = new Models.RevisionInformation { - CommitSha = model.Sha, + CommitSha = repoSha, CompileJobs = new List(), - TestMerges = new List(), //non null vals for api returns - OriginCommitSha = lastOriginCommitSha ?? model.Sha + ActiveTestMerges = new List() //non null vals for api returns }; - DatabaseContext.RevisionInformations.Add(revisionInfo); - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + lock (DatabaseContext) //cleaner this way + DatabaseContext.RevisionInformations.Add(revisionInfo); } + revInfoSink?.Invoke(revisionInfo); + return needsDbUpdate; + } + async Task PopulateApi(Repository model, Components.Repository.IRepository repository, string lastOriginCommitSha, Action revInfoSink, CancellationToken cancellationToken) + { + model.IsGitHub = repository.IsGitHubRepository; + model.Origin = repository.Origin; + model.Reference = repository.Reference; + model.Sha = repository.Head; + + //rev info stuff + Models.RevisionInformation revisionInfo = null; + var needsDbUpdate = await LoadRevisionInformation(repository, x => revisionInfo = x, cancellationToken).ConfigureAwait(false); + revisionInfo.OriginCommitSha = lastOriginCommitSha ?? model.Sha; + revInfoSink?.Invoke(revisionInfo); model.RevisionInformation = revisionInfo.ToApi(); return needsDbUpdate; } @@ -102,7 +120,7 @@ namespace Tgstation.Server.Host.Controllers //clone conflict return Conflict(); var api = currentModel.ToApi(); - await PopulateApi(api, repo, null, cancellationToken).ConfigureAwait(false); + await PopulateApi(api, repo, null, null, cancellationToken).ConfigureAwait(false); currentModel.LastOriginCommitSha = repo.Head; await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); return Json(api); @@ -146,7 +164,164 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { - if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, cancellationToken).ConfigureAwait(false)) + if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false)) + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + return Json(api); + } + } + + /// + [TgsAuthorize(RepositoryRights.ChangeAutoUpdateSettings | RepositoryRights.ChangeCommitter | RepositoryRights.ChangeCredentials | RepositoryRights.ChangeTestMergeCommits | RepositoryRights.MergePullRequest | RepositoryRights.SetReference | RepositoryRights.SetSha | RepositoryRights.UpdateBranch)] + public override async Task Update([FromBody]Repository model, CancellationToken cancellationToken) + { + if (model == null) + return BadRequest(new { message = "Missing request model!" }); + + if (model.AccessUser == null ^ model.AccessToken == null) + return BadRequest(new { message = "Either both accessToken and accessUser must be present or neither!" }); + + if (model.Sha != null && model.Reference != null) + return BadRequest(new { message = "Only one of sha or reference may be specified!" }); + + if(model.Sha != null && model.UpdateReference == true) + return BadRequest(new { message = "Cannot update a reference when checking out a sha!" }); + + if (model.Origin != null) + return BadRequest(new { message = "origin cannot be modified without deleting the repository!" }); + + var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0; + var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository); + if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest)) + return Forbid(); + + var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + + if (currentModel == default) + return StatusCode((int)HttpStatusCode.Gone); + + bool CheckModified(Expression> expression, RepositoryRights requiredRight) + { + var memberSelectorExpression = (MemberExpression)expression.Body; + var property = (PropertyInfo)memberSelectorExpression.Member; + + var newVal = property.GetValue(model); + if (newVal == null) + return false; + if (!userRights.HasFlag(requiredRight) && property.GetValue(currentModel) != newVal) + return true; + + property.SetValue(currentModel, newVal); + return false; + }; + + if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials) + || CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials) + || CheckModified(x => x.AutoUpdatesKeepTestMerges, RepositoryRights.ChangeAutoUpdateSettings) + || CheckModified(x => x.AutoUpdatesSynchronize, RepositoryRights.ChangeAutoUpdateSettings) + || CheckModified(x => x.CommitterEmail, RepositoryRights.ChangeCommitter) + || CheckModified(x => x.CommitterName, RepositoryRights.ChangeCommitter) + || CheckModified(x => x.PushTestMergeCommits, RepositoryRights.ChangeTestMergeCommits) + || CheckModified(x => x.ShowTestMergeCommitters, RepositoryRights.ChangeTestMergeCommits)) + return Forbid(); + + //so that's the stuff that is just db, the rest is tricky + + using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + if (newTestMerges && !repo.IsGitHubRepository) + return Conflict(new { message = "Cannot test merge on a non GitHub based repository!" }); + + var committerName = currentModel.ShowTestMergeCommitters.Value ? AuthenticationContext.User.Name : currentModel.CommitterName; + var accessString = GetAccessString(currentModel); + if (model.UpdateReference == true) + { + if (!repo.Tracking && model.Reference == null) + return Conflict(new { message = "Not on an updatable reference!" }); + if (!userRights.HasFlag(RepositoryRights.UpdateBranch)) + return Forbid(); + await repo.FetchOrigin(accessString, cancellationToken).ConfigureAwait(false); + if (model.Sha == null && model.Reference == null) + await repo.MergeOrigin(committerName, currentModel.CommitterEmail, cancellationToken).ConfigureAwait(false); + } + + if (model.Sha != null || model.Reference != null) + { + await repo.CheckoutObject(model.Sha ?? model.Reference, cancellationToken).ConfigureAwait(false); + + if (model.UpdateReference == true && model.Reference != null) + { + if (!repo.Tracking) + return Conflict(new { message = "Checked out reference is does not track an object!" }); + await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); + } + } + + //now add any testmerges + if (newTestMerges) + { + var repoOwner = repo.GitHubOwner; + var repoName = repo.GitHubRepoName; + var allAddedTestMerges = new List(); + foreach (var I in model.NewTestMerges) + { + await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, accessString, cancellationToken).ConfigureAwait(false); + + Octokit.PullRequest pr = null; + string errorMessage = null; + + Models.RevisionInformation revisionInformation = null; + var revInfoTask = LoadRevisionInformation(repo, x => revisionInformation = x, cancellationToken); + + try + { + pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false); + } + catch (Octokit.RateLimitExceededException) + { + //you look at your anonymous access and sigh + errorMessage = "PRE RATE LIMITED"; + } + catch (Octokit.NotFoundException) + { + //you look at your shithub access and sigh + errorMessage = "PRE NOT FOUND"; + } + + var attachedContextUser = new Models.User + { + Id = AuthenticationContext.User.Id + }; + DatabaseContext.Users.Attach(attachedContextUser); + + var tm = new Models.TestMerge + { + Author = pr?.User.Login ?? errorMessage, + BodyAtMerge = pr?.Body, + MergedAt = DateTimeOffset.Now, + TitleAtMerge = pr?.Title ?? errorMessage, + Comment = I.Comment, + Number = I.Number, + MergedBy = attachedContextUser, + PullRequestRevision = I.PullRequestRevision, + Url = pr?.HtmlUrl ?? errorMessage + }; + if (pr == null) + tm.BodyAtMerge = errorMessage; + + allAddedTestMerges.Add(tm); + + await revInfoTask.ConfigureAwait(false); + + revisionInformation.PrimaryTestMerge = tm; + revisionInformation.ActiveTestMerges.AddRange(allAddedTestMerges.Select(x => new RevInfoTestMerge + { + TestMerge = x + })); + } + } + + var api = currentModel.ToApi(); + if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false) || newTestMerges) await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); return Json(api); } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 5c52b6d5f6..52a16491a4 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -56,6 +56,11 @@ namespace Tgstation.Server.Host.Models /// public DbSet ReattachInformations { get; set; } + /// + /// The s om the + /// + public DbSet RevInfoTestMerges { get; set; } + /// public DbSet WatchdogReattachInformations { get; set; } @@ -96,9 +101,12 @@ namespace Tgstation.Server.Host.Models modelBuilder.Entity().HasIndex(x => new { x.UserId, x.InstanceId }).IsUnique(); + modelBuilder.Entity().HasMany(x => x.RevisonInformations).WithOne(x => x.TestMerge).OnDelete(DeleteBehavior.Cascade); + var revInfo = modelBuilder.Entity(); revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); - revInfo.HasMany(x => x.TestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); + revInfo.HasMany(x => x.ActiveTestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade); + revInfo.HasOne(x => x.PrimaryTestMerge).WithOne(x => x.PrimaryRevisionInformation).OnDelete(DeleteBehavior.SetNull); revInfo.HasIndex(x => x.CommitSha).IsUnique(); var chatChannel = modelBuilder.Entity(); diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 2a9eef753e..18fda50bfb 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.Models /// public Repository ToApi() => new Repository { - AccessToken = AccessToken, + //AccessToken = AccessToken, //never show this AccessUser = AccessUser, AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges, AutoUpdatesSynchronize = AutoUpdatesSynchronize, diff --git a/src/Tgstation.Server.Host/Models/RevInfoTestMerge.cs b/src/Tgstation.Server.Host/Models/RevInfoTestMerge.cs new file mode 100644 index 0000000000..826b124780 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/RevInfoTestMerge.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Many to many relationship for and + /// + public sealed class RevInfoTestMerge + { + /// + /// The row Id + /// + public long Id { get; set; } + + /// + /// The + /// + [Required] + public TestMerge TestMerge { get; set; } + + /// + /// The + /// + [Required] + public RevisionInformation RevisionInformation { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 23db58d0f8..4ff7c0bd79 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -17,9 +17,14 @@ namespace Tgstation.Server.Host.Models public Instance Instance { get; set; } /// - /// See + /// See /// - public List TestMerges { get; set; } + public TestMerge PrimaryTestMerge { get; set; } + + /// + /// See + /// + public List ActiveTestMerges { get; set; } /// /// See s made from this @@ -31,7 +36,8 @@ namespace Tgstation.Server.Host.Models { CommitSha = CommitSha, OriginCommitSha = OriginCommitSha, - TestMerges = TestMerges.Select(x => x.ToApi()).ToList(), + PrimaryTestMerge = PrimaryTestMerge?.ToApi(), + ActiveTestMerges = ActiveTestMerges.Select(x => x.TestMerge.ToApi()).ToList(), CompileJobs = CompileJobs.Select(x => x.ToApi()).ToList() }; } diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index c4f6404f31..2428a192a5 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Host.Models { @@ -12,10 +13,19 @@ namespace Tgstation.Server.Host.Models public User MergedBy { get; set; } /// - /// The for the + /// The initial the was merged with /// - [Required] - public RevisionInformation RevisionInformation { get; set; } + public RevisionInformation PrimaryRevisionInformation { get; set; } + + /// + /// Foreign key for + /// + public long? PrimaryRevisionInformationId { get; set; } + + /// + /// All the for the + /// + public List RevisonInformations { get; set; } /// public Api.Models.TestMerge ToApi() => new Api.Models.TestMerge diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs index 871269f3ae..7c4aa091f2 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs @@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Security var res = NativeMethods.LogonUser(splits.Length > 1 ? splits[1] : splits[0], splits.Length > 1 ? splits[0] : null, password, 3 /*LOGON32_LOGON_NETWORK*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out var token); if (!res) - throw new Win32Exception(Marshal.GetLastWin32Error()); + return null; using (var handle = new SafeAccessTokenHandle(token)) //checked internally, windows identity always duplicates the handle when constructed with a userToken return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); //https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271 From 3337d4c6743f98fd2c4815966a2912b0aa5eadd4 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 01:37:48 -0400 Subject: [PATCH 365/397] Minor forgettums --- .../Models/Internal/RepositorySettings.cs | 6 --- src/Tgstation.Server.Api/Models/Repository.cs | 6 +++ .../Controllers/RepositoryController.cs | 38 ++++++++++++++++--- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index 4afc3d8394..b3fd28489f 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -9,12 +9,6 @@ namespace Tgstation.Server.Api.Models.Internal [Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)] public class RepositorySettings { - /// - /// The origin URL. If , the does not exist - /// - [Permissions(WriteRight = RepositoryRights.SetOrigin)] - public string Origin { get; set; } - /// /// The last commit recognized from /// diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 004be123cf..3cec95577c 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -8,6 +8,12 @@ namespace Tgstation.Server.Api.Models /// public sealed class Repository : Internal.RepositorySettings { + /// + /// The origin URL. If , the does not exist + /// + [Permissions(WriteRight = RepositoryRights.SetOrigin)] + public string Origin { get; set; } + /// /// The commit HEAD points to /// diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index bded31639d..11a20fa482 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -32,6 +33,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly Octokit.IGitHubClient gitHubClient; + /// + /// The for the + /// + readonly IJobManager jobManager; + /// /// Construct a /// @@ -39,10 +45,12 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The value of /// The value of - public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, Octokit.IGitHubClient gitHubClient) : base(databaseContext, authenticationContextFactory, true) + /// The value of + public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, Octokit.IGitHubClient gitHubClient, IJobManager jobManager) : base(databaseContext, authenticationContextFactory, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); } static string GetAccessString(Api.Models.Internal.RepositorySettings repositorySettings) => repositorySettings.AccessUser != null ? String.Concat(repositorySettings.AccessUser, '@', repositorySettings.AccessToken) : null; @@ -109,12 +117,12 @@ namespace Tgstation.Server.Host.Controllers currentModel.AccessToken = model.AccessToken; - currentModel.AccessUser = model.AccessUser; - currentModel.Origin = model.Origin; //intentionally only these fields, user not allowed to change anything else atm + currentModel.AccessUser = model.AccessUser; //intentionally only these fields, user not allowed to change anything else atm var cloneBranch = model.Reference; + var origin = model.Origin; var repoManager = instanceManager.GetInstance(Instance).RepositoryManager; - using (var repo = await repoManager.CloneRepository(new Uri(currentModel.Origin), cloneBranch, GetAccessString(currentModel), cancellationToken).ConfigureAwait(false)) + using (var repo = await repoManager.CloneRepository(new Uri(origin), cloneBranch, GetAccessString(currentModel), cancellationToken).ConfigureAwait(false)) { if (repo == null) //clone conflict @@ -140,7 +148,6 @@ namespace Tgstation.Server.Host.Controllers if (currentModel == default) return StatusCode((int)HttpStatusCode.Gone); - currentModel.Origin = null; currentModel.LastOriginCommitSha = null; currentModel.AccessToken = null; currentModel.AccessUser = null; @@ -228,6 +235,8 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { + var startSha = repo.Head; + if (newTestMerges && !repo.IsGitHubRepository) return Conflict(new { message = "Cannot test merge on a non GitHub based repository!" }); @@ -323,6 +332,25 @@ namespace Tgstation.Server.Host.Controllers var api = currentModel.ToApi(); if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false) || newTestMerges) await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + + //synchronize now because we don't care if we fail so its safe to fire/forget the job + + if (startSha != repo.Head) + { + var job = new Models.Job + { + Description = "Synchronize repository changes", + StartedBy = AuthenticationContext.User, + Instance = Instance + }; + await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => + { + using (var repos = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + if (repos != null) + await repos.Sychronize(accessString, ct).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); + } + return Json(api); } } From 0d465e00abe174281ed264334843769a7bf233b4 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 01:46:56 -0400 Subject: [PATCH 366/397] Das fix --- src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index b3fd28489f..d75df89312 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Api.Models.Internal public class RepositorySettings { /// - /// The last commit recognized from + /// The last commit recognized from the origin remote /// [Permissions(DenyWrite = true)] public string LastOriginCommitSha { get; set; } From bfaa4b81d39cece9d77209be596c5ec5560fa2b0 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 01:57:11 -0400 Subject: [PATCH 367/397] Minor notes --- .../Controllers/ConfigurationController.cs | 2 +- v4_prototype_TODO.txt | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index 903d08eee9..e4d33d6e1b 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Controllers /// /// The for s /// - [Route("/Configuration")] + [Route("/" + nameof(Configuration))] public sealed class ConfigurationController : ModelController { /// diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index fe4a87603e..90474044b8 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -1,22 +1,24 @@ Watchdog performance counters - Remember to catch PlatformNotSupportedException() -Event scripts - -Repo synchronization - -Repo controller - Install as network service instead of LocalSystem if win10 >= Creators update to allow for unprivileged symlinks Nuspec stuff for api and client Verify the byond cache folder location on linux -Map hosting address and certificate directly from configuration - Server updates. Use FOLDERS so dependencies can be properly packaged +-Fix this + +ByondController TgsAuthorize attribute |'ing doesn't work due to role names, change to params[] +OR use pow2 to go up and reverse tocode the enums -Don't throw arg null exceptions with null [FromBody]'s, apparently that's allowed, return bad request instead \ No newline at end of file +Don't throw arg null exceptions with null [FromBody]'s, apparently that's allowed, return bad request instead + +Test repo +Test byond +Test compile +Test watchdog +test configuration \ No newline at end of file From ef1ae99fab2174942aa29a19e5378dc7951b15ec Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 09:19:45 -0400 Subject: [PATCH 368/397] ByondController --- src/Tgstation.Server.Api/Models/Byond.cs | 6 + src/Tgstation.Server.Api/Models/Repository.cs | 6 + .../Rights/ByondRights.cs | 12 +- .../Rights/RepositoryRights.cs | 10 +- .../Components/Byond/ByondManager.cs | 29 ++++- .../Components/Byond/IByondManager.cs | 8 +- .../Components/Repository/Repository.cs | 7 +- .../Repository/RepositoryManager.cs | 46 +++++--- .../Controllers/ByondController.cs | 108 ++++++++++++++++++ .../Controllers/DreamDaemonController.cs | 9 +- .../Controllers/RepositoryController.cs | 65 +++++++++-- src/Tgstation.Server.Host/Core/JobManager.cs | 13 +++ 12 files changed, 282 insertions(+), 37 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/ByondController.cs diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index 17f5b1456c..9eebf4ddd1 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -14,5 +14,11 @@ namespace Tgstation.Server.Api.Models /// [Permissions(ReadRight = ByondRights.ReadActive, WriteRight = ByondRights.ChangeVersion)] public Version Version { get; set; } + + /// + /// The being used to install a new + /// + [Permissions(DenyWrite = true)] + public Job InstallJob { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 3cec95577c..981c77aa94 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -32,6 +32,12 @@ namespace Tgstation.Server.Api.Models [Permissions(DenyWrite = true)] public bool? IsGitHub { get; set; } + /// + /// The started by the if any + /// + [Permissions(DenyWrite = true)] + public Job ActiveJob { get; set; } + /// /// Do the equivalent of a git pull. Will attempt to merge unless is also specified /// diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs index ae650b69af..ed08ed282a 100644 --- a/src/Tgstation.Server.Api/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -17,8 +17,16 @@ namespace Tgstation.Server.Api.Rights /// ReadActive = 1, /// - /// User may change to any BYOND version + /// User may list all installed BYOND versions /// - ChangeVersion = 4 + ListInstalled = 2, + /// + /// User may change the active BYOND version + /// + ChangeVersion = 4, + /// + /// User may cancel version installations + /// + CancelInstall = 8 } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index fa86528d28..5eb33c11b7 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -55,6 +55,14 @@ namespace Tgstation.Server.Api.Rights /// /// User may delete the /// - Delete = 2048 + Delete = 2048, + /// + /// User may cancel clone operations + /// + CancelClone = 4096, + /// + /// User may cancel synchronize operations + /// + CancelSynchronize = 8192 } } diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index f6f0c44790..6e5079e76c 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -5,6 +5,7 @@ using System.Linq; 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 @@ -20,6 +21,16 @@ namespace Tgstation.Server.Host.Components.Byond /// public Version ActiveVersion { get; private set; } + /// + public IReadOnlyList InstalledVersions + { + get + { + lock (installedVersions) + return installedVersions.Select(x => Version.Parse(x.Key)).ToList(); + } + } + /// /// The for the /// @@ -40,6 +51,13 @@ namespace Tgstation.Server.Host.Components.Byond /// readonly Dictionary installedVersions; + /// + /// The for the + /// + readonly SemaphoreSlim semaphore; + + static string VersionKey(Version version) => new Version(version.Major, version.Minor).ToString(); + /// /// Construct a /// @@ -53,9 +71,11 @@ namespace Tgstation.Server.Host.Components.Byond this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); installedVersions = new Dictionary(); + semaphore = new SemaphoreSlim(1); } - static string VersionKey(Version version) => new Version(version.Major, version.Minor).ToString(); + /// + public void Dispose() => semaphore.Dispose(); async Task InstallVersion(Version version, CancellationToken cancellationToken) { @@ -95,8 +115,11 @@ namespace Tgstation.Server.Host.Components.Byond public async Task ChangeVersion(Version version, CancellationToken cancellationToken) { await InstallVersion(version, cancellationToken).ConfigureAwait(false); - await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); - ActiveVersion = version; + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + { + await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); + ActiveVersion = version; + } } /// diff --git a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs index 70fd934081..767d15537e 100644 --- a/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/IByondManager.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Hosting; using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8,13 +9,18 @@ namespace Tgstation.Server.Host.Components.Byond /// /// For managing the BYOND installation /// - public interface IByondManager : IHostedService + public interface IByondManager : IHostedService, IDisposable { /// /// The currently active BYOND version /// Version ActiveVersion { get; } + /// + /// The installed BYOND versions + /// + IReadOnlyList InstalledVersions { get; } + /// /// Change the active BYOND version /// diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 13fdf97475..11e516ee2a 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -12,6 +12,11 @@ namespace Tgstation.Server.Host.Components.Repository /// sealed class Repository : IRepository { + /// + /// Indication of a GitHub repository + /// + public const string GitHubUrl = "://github.com/"; + const string UnknownReference = ""; /// @@ -86,7 +91,7 @@ namespace Tgstation.Server.Host.Components.Repository this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); - IsGitHubRepository = Origin.ToUpperInvariant().Contains("://GITHUB.COM/"); + IsGitHubRepository = Origin.ToUpperInvariant().Contains(GitHubUrl.ToUpperInvariant()); if (IsGitHubRepository) { GetRepositoryOwnerName(Origin, out var owner, out var name); diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index ecaf3c5d98..25cd954457 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -53,28 +53,38 @@ namespace Tgstation.Server.Host.Components.Repository { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false)) - { - await DeleteRepository(cancellationToken).ConfigureAwait(false); - - await Task.Factory.StartNew(() => + try + { + await DeleteRepository(cancellationToken).ConfigureAwait(false); + + await Task.Factory.StartNew(() => + { + string path = null; + try + { + path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url.ToString(), accessString), ioManager.ResolvePath("."), new CloneOptions + { + OnProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + RecurseSubmodules = true, + OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, + RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested, + BranchName = initialBranch + }); + } + catch (UserCancelledException) { } + cancellationToken.ThrowIfCancellationRequested(); + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + } + catch { - string path = null; try { - path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url.ToString(), accessString), ioManager.ResolvePath("."), new CloneOptions - { - OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, - RecurseSubmodules = true, - OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, - RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested, - BranchName = initialBranch - }); + await ioManager.DeleteDirectory(".", default).ConfigureAwait(false); } - catch (UserCancelledException) { } - cancellationToken.ThrowIfCancellationRequested(); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - } + catch { } + throw; + } return await LoadRepository(cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs new file mode 100644 index 0000000000..5d731e6060 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -0,0 +1,108 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// Controller for managing s + /// + [Route("/" + nameof(Byond))] + public sealed class ByondController : ModelController + { + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// The for the + /// + readonly IJobManager jobManager; + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + /// The value of + /// The value of + public ByondController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IJobManager jobManager, ILogger logger) : base(databaseContext, authenticationContextFactory, true) + { + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + [TgsAuthorize(ByondRights.ReadActive)] + public override Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult) + Json(new Api.Models.Byond + { + Version = instanceManager.GetInstance(Instance).ByondManager.ActiveVersion + })); + + /// + [TgsAuthorize(ByondRights.ListInstalled)] + public override Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult) + Json(instanceManager.GetInstance(Instance).ByondManager.InstalledVersions.Select(x => new Api.Models.Byond + { + Version = x + }))); + + /// + [TgsAuthorize(ByondRights.ChangeVersion)] + public override async Task Update([FromBody] Api.Models.Byond model, CancellationToken cancellationToken) + { + if (model == null) + return BadRequest(new { message = "Missing request model!" }); + + if(model.Version == null) + return BadRequest(new { message = "Missing version!" }); + + var byondManager = instanceManager.GetInstance(Instance).ByondManager; + + //remove cruff fields + var installingVersion = new Version(model.Version.Major, model.Version.Major); + + var result = new Api.Models.Byond(); + + if (byondManager.InstalledVersions.Any(x => x == model.Version)) + { + logger.LogInformation("User ID {0} changing instance ID {1} BYOND version to {2}", AuthenticationContext.User.Id, Instance.Id, installingVersion); + await byondManager.ChangeVersion(model.Version, cancellationToken).ConfigureAwait(false); + } + else + { + logger.LogInformation("User ID {0} installing BYOND version to {2} on instance ID {1}", AuthenticationContext.User.Id, Instance.Id, installingVersion); + //run the install through the job manager + var job = new Models.Job + { + StartedBy = AuthenticationContext.User, + CancelRightsType = RightsType.Byond, + CancelRight = (int)ByondRights.CancelInstall, + Instance = Instance + }; + await jobManager.RegisterOperation(job, (paramJob, serviceProvicer, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false); + result.InstallJob = job.ToApi(); + } + result.Version = byondManager.ActiveVersion; + return Json(result); + } + } +} diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 0167d49112..323e073e00 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -55,15 +55,16 @@ namespace Tgstation.Server.Host.Controllers if (instance.Watchdog.Running) return StatusCode((int)HttpStatusCode.Gone); - await jobManager.RegisterOperation(new Models.Job + var job = new Models.Job { Description = "Launch DreamDaemon", CancelRight = (int)DreamDaemonRights.Shutdown, CancelRightsType = RightsType.DreamDaemon, Instance = Instance, StartedBy = AuthenticationContext.User - }, - async (job, serviceProvider, innerCt) => + }; + await jobManager.RegisterOperation(job, + async (paramJob, serviceProvider, innerCt) => { var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false); if (result == null) @@ -72,7 +73,7 @@ namespace Tgstation.Server.Host.Controllers throw new Exception("Failed to launch watchdog!"); }, cancellationToken).ConfigureAwait(false); - return Ok(); + return Json(job); } /// diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 11a20fa482..aeac86e77c 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -38,6 +39,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly IJobManager jobManager; + /// + /// The for the + /// + readonly ILogger logger; + /// /// Construct a /// @@ -46,11 +52,13 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, Octokit.IGitHubClient gitHubClient, IJobManager jobManager) : base(databaseContext, authenticationContextFactory, true) + /// The value of + public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, Octokit.IGitHubClient gitHubClient, IJobManager jobManager, ILogger logger) : base(databaseContext, authenticationContextFactory, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } static string GetAccessString(Api.Models.Internal.RepositorySettings repositorySettings) => repositorySettings.AccessUser != null ? String.Concat(repositorySettings.AccessUser, '@', repositorySettings.AccessToken) : null; @@ -115,6 +123,13 @@ namespace Tgstation.Server.Host.Controllers if (currentModel == default) return StatusCode((int)HttpStatusCode.Gone); + //normalize github urls + const string BadGitHubUrl = "://www.github.com/"; + var uiOrigin = model.Origin.ToUpperInvariant(); + var uiBad = BadGitHubUrl.ToUpperInvariant(); + var uiGitHub = Components.Repository.Repository.GitHubUrl.ToUpperInvariant(); + if (uiOrigin.Contains(uiBad)) + model.Origin = uiOrigin.Replace(uiBad, uiGitHub); currentModel.AccessToken = model.AccessToken; currentModel.AccessUser = model.AccessUser; //intentionally only these fields, user not allowed to change anything else atm @@ -122,15 +137,37 @@ namespace Tgstation.Server.Host.Controllers var origin = model.Origin; var repoManager = instanceManager.GetInstance(Instance).RepositoryManager; - using (var repo = await repoManager.CloneRepository(new Uri(origin), cloneBranch, GetAccessString(currentModel), cancellationToken).ConfigureAwait(false)) + + using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { if (repo == null) //clone conflict return Conflict(); + + var job = new Models.Job + { + Description = "Clone repository", + StartedBy = AuthenticationContext.User, + CancelRightsType = RightsType.Repository, + CancelRight = (int)RepositoryRights.CancelClone, + Instance = Instance + }; var api = currentModel.ToApi(); - await PopulateApi(api, repo, null, null, cancellationToken).ConfigureAwait(false); - currentModel.LastOriginCommitSha = repo.Head; - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => + { + using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, GetAccessString(currentModel), cancellationToken).ConfigureAwait(false)) + { + if (repos == null) + throw new Exception("Filesystem conflict while cloning repository!"); + await PopulateApi(api, repo, null, null, cancellationToken).ConfigureAwait(false); + } + }, cancellationToken).ConfigureAwait(false); + + api.Origin = model.Origin; + api.Reference = model.Reference; + api.IsGitHub = model.Origin.ToUpperInvariant().Contains(uiGitHub); + api.ActiveJob = job.ToApi(); + return Json(api); } } @@ -154,7 +191,17 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - await instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken).ConfigureAwait(false); + logger.LogInformation("Instance {0} repository delete initiated by user {1}", Instance.Id, AuthenticationContext.User.Id); + + var job = new Models.Job + { + Description = "Delete repository", + StartedBy = AuthenticationContext.User, + Instance = Instance + }; + var api = currentModel.ToApi(); + await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false); + api.ActiveJob = job.ToApi(); return Ok(); } @@ -341,7 +388,9 @@ namespace Tgstation.Server.Host.Controllers { Description = "Synchronize repository changes", StartedBy = AuthenticationContext.User, - Instance = Instance + Instance = Instance, + CancelRightsType = RightsType.Repository, + CancelRight = (int)RepositoryRights.CancelSynchronize, }; await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => { @@ -349,6 +398,8 @@ namespace Tgstation.Server.Host.Controllers if (repos != null) await repos.Sychronize(accessString, ct).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false); + + api.ActiveJob = job.ToApi(); } return Json(api); diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 2f0dcc2a94..c99672c654 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -104,6 +104,19 @@ namespace Tgstation.Server.Host.Core { var databaseContext = scope.ServiceProvider.GetRequiredService(); job.StartedAt = DateTimeOffset.Now; + job.Instance = new Instance + { + Id = job.Instance.Id + }; + databaseContext.Instances.Attach(job.Instance); + if (job.StartedBy != null) + { + job.StartedBy = new User + { + Id = job.StartedBy.Id + }; + databaseContext.Users.Attach(job.StartedBy); + } databaseContext.Jobs.Add(job); await databaseContext.Save(cancellationToken).ConfigureAwait(false); var jobHandler = JobHandler.Create(x => RunJob(job, operation, x)); From 3795de7a959be154b132b14cc2f55157775c9008 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 09:21:14 -0400 Subject: [PATCH 369/397] OH GOD THAT"S HOW SQL COMPARES STRINGS???? --- src/Tgstation.Server.Host/Controllers/UserController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 25bd803e95..607a7900d6 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -60,6 +60,10 @@ namespace Tgstation.Server.Host.Controllers if (!(model.Password == null ^ model.SystemIdentifier == null)) return BadRequest(new { message = "User must have exactly one of either a password or system identifier!" }); + model.Name = model.Name?.Trim(); + if (model.Name?.Length == 0) + model.Name = null; + if (!(model.Name == null ^ model.SystemIdentifier == null)) return BadRequest(new { message = "User must have a name if and only if user has no system identifier!" }); From 729916ff6300519c256abd87fb6abe21e49f68cd Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 09:22:08 -0400 Subject: [PATCH 370/397] Remove useless suppression --- src/Tgstation.Server.Host/Controllers/UserController.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 607a7900d6..67161f1db1 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -74,9 +74,7 @@ namespace Tgstation.Server.Host.Controllers CreatedBy = AuthenticationContext.User, Enabled = model.Enabled ?? false, InstanceManagerRights = model.InstanceManagerRights ?? InstanceManagerRights.None, -#pragma warning disable CA1308 // Normalize strings to uppercase Name = model.Name, -#pragma warning restore CA1308 // Normalize strings to uppercase SystemIdentifier = model.SystemIdentifier, InstanceUsers = new List() }; From 9217074bd5d078eab9c4b24b8f6eacbb049455d4 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 09:23:14 -0400 Subject: [PATCH 371/397] Small test merging optimization --- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index aeac86e77c..d8bb957161 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -320,6 +320,8 @@ namespace Tgstation.Server.Host.Controllers var allAddedTestMerges = new List(); foreach (var I in model.NewTestMerges) { + var prTask = gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number); + await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, accessString, cancellationToken).ConfigureAwait(false); Octokit.PullRequest pr = null; @@ -330,7 +332,7 @@ namespace Tgstation.Server.Host.Controllers try { - pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false); + pr = await prTask.ConfigureAwait(false); } catch (Octokit.RateLimitExceededException) { From 7bd7043c74223839d11c63b47c4e9a027b9e18d5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 10:09:17 -0400 Subject: [PATCH 372/397] Fixups --- .../Components/EventConsumer.cs | 4 ++- .../Controllers/ApiController.cs | 28 +++++++++++++------ .../Controllers/InstanceController.cs | 28 +++++++++++++------ .../Controllers/InstanceUserController.cs | 12 ++------ .../Controllers/RepositoryController.cs | 4 +-- .../Models/DatabaseContext.cs | 4 +-- .../Models/IDatabaseContext.cs | 5 ++++ .../Security/AuthenticationContextFactory.cs | 8 ++---- v4_prototype_TODO.txt | 2 -- 9 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/EventConsumer.cs b/src/Tgstation.Server.Host/Components/EventConsumer.cs index 83e1c5ae69..e28d93051f 100644 --- a/src/Tgstation.Server.Host/Components/EventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/EventConsumer.cs @@ -46,7 +46,9 @@ namespace Tgstation.Server.Host.Components /// The value of public void SetWatchdog(IWatchdog watchdog) { - if (watchdog != null) + if (watchdog == null) + throw new ArgumentNullException(nameof(watchdog)); + if (this.watchdog != null) throw new InvalidOperationException("watchdog already set!"); this.watchdog = watchdog; } diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 5cdc49a972..803f3c29b5 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -92,9 +92,11 @@ namespace Tgstation.Server.Host.Controllers var claims = new List(); foreach (RightsType I in enumerator) { - var rightInt = authenticationContext.GetRight(I); + //if there's no instance user, do a weird thing and add all the instance roles + //we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid + var rightInt = RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null ? ~0 : authenticationContext.GetRight(I); var rightEnum = RightsHelper.RightToType(I); - var right = (Enum)Enum.ToObject(rightEnum, authenticationContext.GetRight(I)); + var right = (Enum)Enum.ToObject(rightEnum, rightInt); foreach(Enum J in Enum.GetValues(rightEnum)) if(right.HasFlag(J)) claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J))); @@ -122,17 +124,25 @@ namespace Tgstation.Server.Host.Controllers /// public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - if (requireInstance && AuthenticationContext.InstanceUser == null) - { - //accessing an instance they don't have access to - await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); - return; - } - //validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); + + if (requireInstance) + { + if(!ApiHeaders.InstanceId.HasValue) + { + await BadRequest(new { message = "Missing InstanceId header!" }).ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + if (AuthenticationContext.InstanceUser == null) + { + //accessing an instance they don't have access to or one that's disabled + await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); + return; + } + } } catch (InvalidOperationException e) { diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 1c6a9f666b..40225f90dc 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -68,13 +68,18 @@ namespace Tgstation.Server.Host.Controllers this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - void NormalizeModelPath(Api.Models.Instance model) + void NormalizeModelPath(Api.Models.Instance model, out string absolutePath) { if (model.Path == null) + { + absolutePath = null; return; - model.Path = ioManager.ResolvePath(model.Path); + } + absolutePath = ioManager.ResolvePath(model.Path); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - model.Path = model.Path.ToUpperInvariant(); + model.Path = absolutePath.ToUpperInvariant(); + else + model.Path = absolutePath; } Models.InstanceUser InstanceAdminUser() => new Models.InstanceUser @@ -99,7 +104,7 @@ namespace Tgstation.Server.Host.Controllers if(model.Path == null) return BadRequest(new { message = "path must not be empty!" }); - NormalizeModelPath(model); + NormalizeModelPath(model, out var rawPath); var dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken); if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false)) return Conflict(new { message = "Path not empty!" }); @@ -128,6 +133,8 @@ namespace Tgstation.Server.Host.Controllers CommitterName = application.VersionString, PushTestMergeCommits = false, ShowTestMergeCommitters = true, + AutoUpdatesKeepTestMerges = false, + AutoUpdatesSynchronize = false }, //give this user full privileges on the instance InstanceUsers = new List @@ -144,7 +151,7 @@ namespace Tgstation.Server.Host.Controllers try { //actually reserve it now - await ioManager.CreateDirectory(model.Path, default).ConfigureAwait(false); + await ioManager.CreateDirectory(rawPath, default).ConfigureAwait(false); } catch { @@ -198,14 +205,16 @@ namespace Tgstation.Server.Host.Controllers { var instanceQuery = DatabaseContext.Instances.Where(x => x.Id == model.Id); - var usersInstanceUserTask = instanceQuery.SelectMany(x => x.InstanceUsers).Where(x => x.UserId == AuthenticationContext.User.Id).FirstOrDefaultAsync(cancellationToken); + var usersInstanceUserTask = instanceQuery + .Include(x => x.InstanceUsers) + .SelectMany(x => x.InstanceUsers).Where(x => x.UserId == AuthenticationContext.User.Id).FirstOrDefaultAsync(cancellationToken); var originalModel = await instanceQuery .Include(x => x.RepositorySettings) .Include(x => x.ChatSettings) .ThenInclude(x => x.Channels) .Include(x => x.DreamDaemonSettings) //need these for onlining - .FirstAsync(cancellationToken).ConfigureAwait(false); + .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (originalModel == default(Models.Instance)) return StatusCode((int)HttpStatusCode.Gone); @@ -228,7 +237,7 @@ namespace Tgstation.Server.Host.Controllers string originalModelPath = null; if (model.Path != null) { - NormalizeModelPath(model); + NormalizeModelPath(model, out var rawPath); if (model.Path != originalModel.Path) { @@ -274,7 +283,8 @@ namespace Tgstation.Server.Host.Controllers { logger.LogError("Error changing instance online state! Exception: {0}", e); originalModel.Online = originalOnline; - originalModel.Path = originalModelPath; + if (originalModelPath != null) + originalModel.Path = originalModelPath; await DatabaseContext.Save(default).ConfigureAwait(false); throw; } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs index 622771ee6a..adc0d9d4cc 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -73,17 +73,11 @@ namespace Tgstation.Server.Host.Controllers DreamMakerRights = model.DreamMakerRights ?? DreamMakerRights.None, RepositoryRights = model.RepositoryRights ?? RepositoryRights.None, InstanceUserRights = model.InstanceUserRights ?? InstanceUserRights.None, - UserId = model.UserId + UserId = model.UserId, + InstanceId = Instance.Id }; - var ourInstance = new Models.Instance - { - Id = Instance.Id - }; - - DatabaseContext.Instances.Attach(ourInstance); - - ourInstance.InstanceUsers.Add(dbUser); + DatabaseContext.InstanceUsers.Add(dbUser); try { diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index d8bb957161..2f761f9f9c 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -337,12 +337,12 @@ namespace Tgstation.Server.Host.Controllers catch (Octokit.RateLimitExceededException) { //you look at your anonymous access and sigh - errorMessage = "PRE RATE LIMITED"; + errorMessage = "P.R.E. RATE LIMITED"; } catch (Octokit.NotFoundException) { //you look at your shithub access and sigh - errorMessage = "PRE NOT FOUND"; + errorMessage = "P.R.E. NOT FOUND"; } var attachedContextUser = new Models.User diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 52a16491a4..ebdb01a161 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -37,9 +37,7 @@ namespace Tgstation.Server.Host.Models /// public DbSet RepositorySettings { get; set; } - /// - /// The s in the - /// + /// public DbSet InstanceUsers { get; set; } /// diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index 2014075b58..ea02c85426 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -19,6 +19,11 @@ namespace Tgstation.Server.Host.Models /// DbSet Instances { get; } + /// + /// The s in the + /// + DbSet InstanceUsers { get; } + /// /// The s in the /// diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 853dba9481..18fa1536ce 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -49,11 +49,9 @@ namespace Tgstation.Server.Host.Security var userQuery = databaseContext.Users.Where(x => x.Id == userId).FirstAsync(cancellationToken); - var instanceUser = instanceId.HasValue ? (await databaseContext.Users - .Where(x => x.Id == userId) - .Include(x => x.InstanceUsers) - .SelectMany(x => x.InstanceUsers) - .Where(x => x.InstanceId == instanceId && x.Instance.Online.Value) + var instanceUser = instanceId.HasValue ? (await databaseContext.InstanceUsers + .Where(x => x.UserId == userId) + .Where(x => x.InstanceId == instanceId) .Include(x => x.Instance) .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false)) : null; diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 90474044b8..8262e03165 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -10,8 +10,6 @@ Verify the byond cache folder location on linux Server updates. Use FOLDERS so dependencies can be properly packaged -Fix this -ByondController - TgsAuthorize attribute |'ing doesn't work due to role names, change to params[] OR use pow2 to go up and reverse tocode the enums From 7dc4118936bb9106cf9020583a48e68ec77ca7ac Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 10:33:54 -0400 Subject: [PATCH 373/397] Various fixups --- src/Tgstation.Server.Api/Models/Instance.cs | 8 +++++ .../Rights/InstanceManagerRights.cs | 6 +++- .../Components/InstanceManager.cs | 35 +++++++++---------- .../Controllers/ByondController.cs | 2 +- .../Controllers/InstanceController.cs | 35 +++++++++++++++++-- .../Controllers/JobController.cs | 22 +++++------- src/Tgstation.Server.Host/Core/JobManager.cs | 2 ++ 7 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 66e5238161..01e41bbca7 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models @@ -48,6 +49,13 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = InstanceManagerRights.SetAutoUpdate)] public int? AutoUpdateInterval { get; set; } + /// + /// The representing a change of + /// + [Permissions(DenyWrite = true)] + [NotMapped] + public Job MoveJob { get; set; } + /// public Instance CloneMetadata() => new Instance { diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index 7d0a3c4ace..f8997edce3 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -47,6 +47,10 @@ namespace Tgstation.Server.Api.Rights /// /// User can change /// - SetAutoUpdate = 256 + SetAutoUpdate = 256, + /// + /// User can cancel move operations + /// + CancelMove = 512, } } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 44099741d7..323fa7568a 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using System; using System.Collections.Generic; @@ -40,6 +41,11 @@ namespace Tgstation.Server.Host.Components /// readonly IApplication application; + /// + /// The for the + /// + readonly ILogger logger; + /// /// Map of s to respective s /// @@ -56,12 +62,14 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application) + /// The value of + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILogger logger) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); instances = new Dictionary(); interopConsumers = new Dictionary(); @@ -93,24 +101,11 @@ namespace Tgstation.Server.Host.Components if (newPath == null) throw new ArgumentNullException(nameof(newPath)); if (instance.Online.Value) - await OfflineInstance(instance, cancellationToken).ConfigureAwait(false); - Task instanceOnlineTask = null; - try - { - var oldPath = instance.Path; - await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); - instance.Path = ioManager.ResolvePath(newPath); - instanceOnlineTask = OnlineInstance(instance, default); - await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); - } - finally - { - if (instance.Online.Value) - if (instanceOnlineTask == null) - await OnlineInstance(instance, default).ConfigureAwait(false); - else - await instanceOnlineTask.ConfigureAwait(false); - } + throw new InvalidOperationException("Cannot move an online instance!"); + var oldPath = instance.Path; + await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); + instance.Path = ioManager.ResolvePath(newPath); + await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); } /// @@ -118,6 +113,7 @@ namespace Tgstation.Server.Host.Components { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); + logger.LogInformation("Offlining instance ID {0}", metadata.Id); IInstance instance; lock (this) { @@ -140,6 +136,7 @@ namespace Tgstation.Server.Host.Components { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); + logger.LogInformation("Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path); var instance = instanceFactory.CreateInstance(metadata, this); try { diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 5d731e6060..a70c5d80a8 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -78,7 +78,7 @@ namespace Tgstation.Server.Host.Controllers var byondManager = instanceManager.GetInstance(Instance).ByondManager; //remove cruff fields - var installingVersion = new Version(model.Version.Major, model.Version.Major); + var installingVersion = new Version(model.Version.Major, model.Version.Minor); var result = new Api.Models.Byond(); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 40225f90dc..aab876e91b 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -272,27 +272,58 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + var oldAutoStart = originalModel.DreamDaemonSettings.AutoStart; try { if (originalOnline && model.Online.Value == false) await instanceManager.OfflineInstance(originalModel, cancellationToken).ConfigureAwait(false); else if (!originalOnline && model.Online.Value == true) + { + //force autostart false here because we don't want any long running jobs right now + //remember to document this + originalModel.DreamDaemonSettings.AutoStart = false; await instanceManager.OnlineInstance(originalModel, cancellationToken).ConfigureAwait(false); + } } catch (Exception e) { logger.LogError("Error changing instance online state! Exception: {0}", e); originalModel.Online = originalOnline; + originalModel.DreamDaemonSettings.AutoStart = oldAutoStart; if (originalModelPath != null) originalModel.Path = originalModelPath; await DatabaseContext.Save(default).ConfigureAwait(false); throw; } + var api = originalModel.ToApi(); if (originalModelPath != null) - await ioManager.MoveDirectory(originalModelPath, model.Path, cancellationToken).ConfigureAwait(false); + { + var job = new Models.Job + { + Description = "Move instance location", + Instance = Instance, + CancelRightsType = RightsType.InstanceManager, + CancelRight = (int)InstanceManagerRights.CancelMove, + StartedBy = AuthenticationContext.User + }; - return Json(originalModel.ToApi()); + await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => { + try + { + await instanceManager.MoveInstance(Instance, originalModel.Path, ct).ConfigureAwait(false); + } + catch + { + originalModel.Path = originalModelPath; + await DatabaseContext.Save(default).ConfigureAwait(false); + throw; + } + }, cancellationToken).ConfigureAwait(false); + api.MoveJob = job.ToApi(); + } + + return Json(api); } /// diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index a8d2a20739..79bb25cc15 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -37,18 +37,12 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize] public override async Task List(CancellationToken cancellationToken) { - IQueryable query = DatabaseContext.Jobs; - if (Instance != null) + //you KNOW this will need pagination eventually right? + var jobs = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id).OrderByDescending(x => x.StartedAt).Select(x => new Api.Models.Job { - if (AuthenticationContext.InstanceUser?.AnyRights != true) - return Forbid(); - query = query.Where(x => x.Instance.Id == Instance.Id); - } - else - query = query.Where(x => x.Instance == null); - - var jobs = await query.Where(x => x.StoppedAt == null).ToListAsync(cancellationToken).ConfigureAwait(false); - return Json(jobs.Select(x => x.ToApi())); + Id = x.Id + }).ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(jobs); } /// @@ -60,12 +54,12 @@ namespace Tgstation.Server.Host.Controllers if (job == default(Job)) return NotFound(); + if (job.StoppedAt != null) + return StatusCode((int)HttpStatusCode.Gone); + if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0) return Forbid(); - if(job.StoppedAt != null) - return StatusCode((int)HttpStatusCode.Gone); - await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index c99672c654..c72590d5c9 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -172,6 +172,8 @@ namespace Tgstation.Server.Host.Core var databaseContext = scope.ServiceProvider.GetRequiredService(); job = new Job { Id = job.Id }; databaseContext.Jobs.Attach(job); + user = new User { Id = user.Id }; + databaseContext.Users.Attach(user); job.CancelledBy = user; await databaseContext.Save(cancellationToken).ConfigureAwait(false); } From 8ce8018673a2f6588e69219d868df002c31cfdb6 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 11:01:49 -0400 Subject: [PATCH 374/397] Always bringing things together --- .../Models/Internal/Job.cs | 4 ++- .../Models/Internal/User.cs | 2 +- .../Components/Byond/ByondManager.cs | 35 +++++++++++++------ .../Components/Byond/WindowsByondInstaller.cs | 2 +- .../Controllers/ByondController.cs | 2 ++ .../Controllers/InstanceController.cs | 8 +++-- .../Controllers/RepositoryController.cs | 3 +- src/Tgstation.Server.Host/Models/Job.cs | 2 +- 8 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index 1b0104c22d..7738cf0043 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models.Internal @@ -19,6 +20,7 @@ namespace Tgstation.Server.Api.Models.Internal /// English description of the /// [Permissions(DenyWrite = true)] + [Required] public string Description { get; set; } /// @@ -37,7 +39,7 @@ namespace Tgstation.Server.Api.Models.Internal /// When the stopped /// [Permissions(DenyWrite = true)] - public DateTimeOffset StoppedAt { get; set; } + public DateTimeOffset? StoppedAt { get; set; } /// /// If the was cancelled diff --git a/src/Tgstation.Server.Api/Models/Internal/User.cs b/src/Tgstation.Server.Api/Models/Internal/User.cs index b70e868079..1e6ab36b2e 100644 --- a/src/Tgstation.Server.Api/Models/Internal/User.cs +++ b/src/Tgstation.Server.Api/Models/Internal/User.cs @@ -27,7 +27,7 @@ namespace Tgstation.Server.Api.Models.Internal /// [Permissions(DenyWrite = true)] [Required] - public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset? CreatedAt { get; set; } /// /// The SID/UID of the on Windows/POSIX respectively diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 6e5079e76c..21c29d5f8c 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -96,19 +96,27 @@ namespace Tgstation.Server.Host.Components.Byond await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false); return; } + try + { + var downloadTask = byondInstaller.DownloadVersion(version, cancellationToken); - var downloadTask = byondInstaller.DownloadVersion(version, cancellationToken); + //okay up to us to install it then + await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false); + await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false); - //okay up to us to install it then - await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false); - await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false); + await ioManager.ZipToDirectory(versionKey, await downloadTask.ConfigureAwait(false), cancellationToken).ConfigureAwait(false); - await ioManager.ZipToDirectory(versionKey, await downloadTask.ConfigureAwait(false), cancellationToken).ConfigureAwait(false); + await byondInstaller.InstallByond(ioManager.ResolvePath(versionKey), version, 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 - await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); + //make sure to do this last because this is what tells us we have a valid version + await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); + } + catch + { + lock (installedVersions) + installedVersions.Remove(versionKey); + throw; + } } /// @@ -159,7 +167,14 @@ namespace Tgstation.Server.Host.Components.Byond async Task ReadVersion(string path) { - var bytes = await ioManager.ReadAllBytes(ioManager.ConcatPath(path, VersionFileName), cancellationToken).ConfigureAwait(false); + var versionFile = ioManager.ConcatPath(path, VersionFileName); + 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 + return; + } + var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(false); var text = Encoding.UTF8.GetString(bytes); if (Version.TryParse(text, out var version)) { diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index e1f656e0e2..263f39eb7c 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -89,7 +89,7 @@ namespace Tgstation.Server.Host.Components.Byond /// public async Task InstallByond(string path, Version version, CancellationToken cancellationToken) { - var setNoPromptTrustedModeTask = ioManager.WriteAllBytes(ByondDDConfig, Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken); + var setNoPromptTrustedModeTask = ioManager.WriteAllBytes(ioManager.ConcatPath(path, ByondDDConfig), Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken); //after this version lummox made DD depend of directx lol if (version.Major >= 512 && version.Minor >= 1427 && Monitor.TryEnter(this)) diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index a70c5d80a8..0a6a47f408 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -93,6 +94,7 @@ namespace Tgstation.Server.Host.Controllers //run the install through the job manager var job = new Models.Job { + Description = String.Format(CultureInfo.InvariantCulture, "Install BYOND version {0}", installingVersion), StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.Byond, CancelRight = (int)ByondRights.CancelInstall, diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index aab876e91b..53ac454911 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Net; @@ -235,9 +236,10 @@ namespace Tgstation.Server.Host.Controllers }; string originalModelPath = null; + string rawPath = null; if (model.Path != null) { - NormalizeModelPath(model, out var rawPath); + NormalizeModelPath(model, out rawPath); if (model.Path != originalModel.Path) { @@ -301,7 +303,7 @@ namespace Tgstation.Server.Host.Controllers { var job = new Models.Job { - Description = "Move instance location", + Description = String.Format(CultureInfo.InvariantCulture, "Move instance ID {0} from {1} to {2}", Instance.Id, Instance.Path, rawPath), Instance = Instance, CancelRightsType = RightsType.InstanceManager, CancelRight = (int)InstanceManagerRights.CancelMove, @@ -311,7 +313,7 @@ namespace Tgstation.Server.Host.Controllers await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => { try { - await instanceManager.MoveInstance(Instance, originalModel.Path, ct).ConfigureAwait(false); + await instanceManager.MoveInstance(Instance, rawPath, ct).ConfigureAwait(false); } catch { diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 2f761f9f9c..97afc62c66 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Net; @@ -146,7 +147,7 @@ namespace Tgstation.Server.Host.Controllers var job = new Models.Job { - Description = "Clone repository", + Description = String.Format(CultureInfo.InvariantCulture, "Clone branch {1} of repository {0}", origin, cloneBranch), StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.Repository, CancelRight = (int)RepositoryRights.CancelClone, diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 4262ea1aff..0b56cca428 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -29,7 +29,7 @@ namespace Tgstation.Server.Host.Models StartedAt = StartedAt, StoppedAt = StoppedAt, Cancelled = Cancelled, - CancelledBy = CancelledBy.ToApi(), + CancelledBy = CancelledBy?.ToApi(), CancelRight = CancelRight, CancelRightsType = CancelRightsType, Description = Description, From 30ddf287bcc9ed2a8ca5b1e45d3318116efb0dbd Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 11:12:57 -0400 Subject: [PATCH 375/397] Finally successfully installed a BYOND version --- .../Components/Byond/WindowsByondInstaller.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 263f39eb7c..41ae7207d3 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -19,9 +19,13 @@ namespace Tgstation.Server.Host.Components.Byond /// const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; /// - /// BYOND's DreamDaemon config file in the cfg modification directory + /// Directory to byond installation configuration /// - const string ByondDDConfig = "byond/config/daemon.txt"; + const string ByondConfigDir = "byond/config"; + /// + /// BYOND's DreamDaemon config file + /// + const string ByondDDConfig = "daemon.txt"; /// /// Setting to add to to suppress an invisible user prompt for running a trusted mode .dmb /// @@ -89,7 +93,14 @@ namespace Tgstation.Server.Host.Components.Byond /// public async Task InstallByond(string path, Version version, CancellationToken cancellationToken) { - var setNoPromptTrustedModeTask = ioManager.WriteAllBytes(ioManager.ConcatPath(path, ByondDDConfig), Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken); + async Task SetNoPromptTrusted() + { + 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 if (version.Major >= 512 && version.Minor >= 1427 && Monitor.TryEnter(this)) From 3791e1b315d697a26743fe97c93204cc2b11323b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 11:13:30 -0400 Subject: [PATCH 376/397] Strip null values from response JSON --- src/Tgstation.Server.Host/Core/Application.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 832fa0e5e3..7ef52e874c 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; using Octokit; using System; using System.Globalization; @@ -144,7 +145,10 @@ namespace Tgstation.Server.Host.Core }); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs - services.AddMvc(); + services.AddMvc().AddJsonOptions(options => + { + options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; + }); var databaseConfiguration = databaseConfigurationSection.Get(); void ConfigureDatabase(DbContextOptionsBuilder builder) From 1b1d0dc28e4b679c7ce53f56f47a80d880cc495e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 11:21:49 -0400 Subject: [PATCH 377/397] Aggressive nick preservation for IRC --- .../Components/Chat/Providers/IrcProvider.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index adb436d08a..eafd6293a1 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -269,7 +269,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers client.WriteLine("CAP END", Priority.Critical); } - listenTask = Task.Factory.StartNew(() => client.Listen(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + listenTask = Task.Factory.StartNew(() => + { + while (client.IsConnected) + { + client.ListenOnce(true); + client.Listen(false); + //ensure we have the correct nick + if (client.Nickname != nickname && client.GetIrcUser(nickname) == null) + client.RfcNick(nickname); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } catch (Exception e) { From 331eb88fc4221a4ae34addd3ceec6faa8aa4195d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 11:25:52 -0400 Subject: [PATCH 378/397] Improve it slightly --- .../Components/Chat/Providers/IrcProvider.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index eafd6293a1..794421827b 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -269,6 +269,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers client.WriteLine("CAP END", Priority.Critical); } + client.Listen(false); + if (client.Nickname != nickname) + //run this now cause it has to go up and down the pipe for us to get a proper check + client.GetIrcUser(nickname); + listenTask = Task.Factory.StartNew(() => { while (client.IsConnected) From bd35e807f25857ae7da4e2b30f84f0250b3233ea Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 12:55:46 -0400 Subject: [PATCH 379/397] Fix a ton of shit --- .../Models/Internal/Job.cs | 6 +- .../Components/Byond/WindowsByondInstaller.cs | 20 +- .../Components/Compiler/DmbFactory.cs | 2 +- .../Controllers/DreamMakerController.cs | 20 - .../Controllers/ModelController.cs | 2 +- src/Tgstation.Server.Host/Core/JobManager.cs | 16 +- .../IO/DefaultIOManager.cs | 22 +- tools/TGS.postman_collection.json | 383 ++++++++++++++++++ 8 files changed, 433 insertions(+), 38 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index 7738cf0043..b4247221e1 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -33,7 +33,8 @@ namespace Tgstation.Server.Api.Models.Internal /// When the was started /// [Permissions(DenyWrite = true)] - public DateTimeOffset StartedAt { get; set; } + [Required] + public DateTimeOffset? StartedAt { get; set; } /// /// When the stopped @@ -45,7 +46,8 @@ namespace Tgstation.Server.Api.Models.Internal /// If the was cancelled /// [Permissions(DenyWrite = true)] - public bool Cancelled { get; set; } + [Required] + public bool? Cancelled { get; set; } /// /// The of if it can be cancelled diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 41ae7207d3..3e5422c62d 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -5,6 +5,7 @@ using System.Globalization; 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 @@ -12,7 +13,7 @@ namespace Tgstation.Server.Host.Components.Byond /// /// for windows systems /// - sealed class WindowsByondInstaller : IByondInstaller + sealed class WindowsByondInstaller : IByondInstaller, IDisposable { /// /// The URL format string for getting BYOND windows version {0}.{1} zipfile @@ -51,6 +52,11 @@ namespace Tgstation.Server.Host.Components.Byond /// readonly ILogger logger; + /// + /// The for the + /// + readonly SemaphoreSlim semaphore; + /// /// If DirectX was installed /// @@ -66,9 +72,13 @@ namespace Tgstation.Server.Host.Components.Byond this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + semaphore = new SemaphoreSlim(1); installedDirectX = false; } + /// + public void Dispose() => semaphore.Dispose(); + /// public async Task CleanCache(CancellationToken cancellationToken) { @@ -103,8 +113,8 @@ namespace Tgstation.Server.Host.Components.Byond var setNoPromptTrustedModeTask = SetNoPromptTrusted(); //after this version lummox made DD depend of directx lol - if (version.Major >= 512 && version.Minor >= 1427 && Monitor.TryEnter(this)) - try + if (version.Major >= 512 && version.Minor >= 1427 && !installedDirectX) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { if (!installedDirectX) //always install it, it's pretty fast and will do better redundancy checking than us @@ -143,10 +153,6 @@ namespace Tgstation.Server.Host.Components.Byond installedDirectX = true; } } - finally - { - Monitor.Exit(this); - } await setNoPromptTrustedModeTask.ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index 08fffc4f0a..e994fddfae 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -107,7 +107,7 @@ namespace Tgstation.Server.Host.Components.Compiler { if (job == null) throw new ArgumentNullException(nameof(job)); - if (job.DMApiValidated != true || job.Job.Cancelled || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null) + if (job.DMApiValidated != true || job.Job.Cancelled.Value || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null) throw new InvalidOperationException("Cannot load incomplete compile job!"); if (setAsStagedInDb) await databaseContextFactory.UseContext(async db => diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index d25f914517..c9acbb0d6a 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Rights; @@ -73,25 +72,6 @@ namespace Tgstation.Server.Host.Controllers return Json(job); } - /// - [TgsAuthorize(DreamMakerRights.CancelCompile)] - public override async Task Delete(long id, CancellationToken cancellationToken) - { - //alias for cancelling the latest job - var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Select(x => new Job { Id = x.Job.Id, StoppedAt = x.Job.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); - if (job.StoppedAt != null) - return StatusCode((int)HttpStatusCode.Gone); - try - { - await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); - } - catch (InvalidOperationException) //job already stopped - { - return StatusCode((int)HttpStatusCode.Gone); - } - return Ok(); - } - /// [TgsAuthorize(DreamMakerRights.SetDme)] public override async Task Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 69680526fe..c9ba839646 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -68,7 +68,7 @@ namespace Tgstation.Server.Host.Controllers /// The ID of the model to delete /// The for the operation /// A resulting in the of the operation - [HttpDelete] + [HttpDelete("{id}")] public virtual Task Delete(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index c72590d5c9..4d8359e438 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -10,7 +10,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { /// - sealed class JobManager : IHostedService, IJobManager + sealed class JobManager : IHostedService, IJobManager, IDisposable { /// /// The for the @@ -31,6 +31,13 @@ namespace Tgstation.Server.Host.Core jobs = new Dictionary(); } + /// + public void Dispose() + { + foreach (var job in jobs) + job.Value.Dispose(); + } + /// /// Gets the for a given if it exists /// @@ -104,6 +111,7 @@ namespace Tgstation.Server.Host.Core { var databaseContext = scope.ServiceProvider.GetRequiredService(); job.StartedAt = DateTimeOffset.Now; + job.Cancelled = false; job.Instance = new Instance { Id = job.Instance.Id @@ -134,7 +142,7 @@ namespace Tgstation.Server.Host.Core await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); //mark all jobs as cancelled - var enumerator = await databaseContext.Jobs.Where(y => !y.Cancelled && y.StoppedAt == null).Select(y => y.Id).ToAsyncEnumerable().ToList(cancellationToken).ConfigureAwait(false); + var enumerator = await databaseContext.Jobs.Where(y => !y.Cancelled.Value && !y.StoppedAt.HasValue).Select(y => y.Id).ToAsyncEnumerable().ToList(cancellationToken).ConfigureAwait(false); foreach(var I in enumerator) { var job = new Job { Id = I }; @@ -154,9 +162,6 @@ namespace Tgstation.Server.Host.Core return x.Value.Wait(cancellationToken); }); await Task.WhenAll(joinTasks).ConfigureAwait(false); - foreach (var job in jobs) - job.Value.Dispose(); - jobs.Clear(); } /// @@ -175,6 +180,7 @@ namespace Tgstation.Server.Host.Core user = new User { Id = user.Id }; databaseContext.Users.Attach(user); job.CancelledBy = user; + //let either startup or cancellation set job.cancelled await databaseContext.Save(cancellationToken).ConfigureAwait(false); } } diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 865411b1b6..c4b235b3d7 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -265,9 +265,27 @@ namespace Tgstation.Server.Host.IO /// public async Task DownloadFile(Uri url, CancellationToken cancellationToken) { + //DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it using (var wc = new WebClient()) - using (cancellationToken.Register(() => wc.CancelAsync())) - return await wc.DownloadDataTaskAsync(url).ConfigureAwait(false); + { + var tcs = new TaskCompletionSource(); + wc.DownloadDataCompleted += (a, b) => + { + if (b.Error != null) + tcs.TrySetException(b.Error); + else if (b.Cancelled) + tcs.TrySetCanceled(); + else + tcs.TrySetResult(b.Result); + }; + wc.DownloadDataAsync(url); + using (cancellationToken.Register(() => + { + wc.CancelAsync(); //cancelasync alone doesnt do it either! who wrote this!! + tcs.SetCanceled(); + })) + return await tcs.Task.ConfigureAwait(false); + } //ITS STILL FUCKING DOWNLOADING!!! } /// diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index d3692a376d..92ea78f73b 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -842,6 +842,49 @@ }, "response": [] }, + { + "name": "Set ID 1 to only #botbus", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 1,\n\t\"channels\": [\n\t\t{\n\t\t\t\"ircChannel\": \"#botbus\",\n\t\t\t\"isAdminChannel\": true,\n\t\t\t\"isWatchdogChannel\": true\n\t\t}\n\t\t]\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, { "name": "Create r4407 on Disord", "request": { @@ -1199,6 +1242,167 @@ ], "_postman_isSubFolder": true }, + { + "name": "Jobs", + "description": "", + "item": [ + { + "name": "List Ids", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": -1,\n\t\"dreamDaemonRights\": -1,\n\t\"dreamMakerRights\": -1,\n\t\"repositoryRights\": -1,\n\t\"chatSettingsRights\": -1,\n\t\"configurationRights\": -1\n}" + }, + "url": { + "raw": "localhost:5000/Job/List", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Job", + "List" + ] + } + }, + "response": [] + }, + { + "name": "Get ID", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": -1,\n\t\"dreamDaemonRights\": -1,\n\t\"dreamMakerRights\": -1,\n\t\"repositoryRights\": -1,\n\t\"chatSettingsRights\": -1,\n\t\"configurationRights\": -1\n}" + }, + "url": { + "raw": "localhost:5000/Job/10", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Job", + "10" + ] + } + }, + "response": [] + }, + { + "name": "Cancel ID", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "localhost:5000/Job/11", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Job", + "11" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "a3f3a2f7-5700-48a9-88bf-d18ac2a1855c", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "787a5a17-ad9c-4772-9052-85d81e87d4a9", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "_postman_isSubFolder": true + }, { "name": "Online Instance ID 1", "request": { @@ -1397,6 +1601,185 @@ } ] }, + { + "name": "Byond", + "description": "", + "item": [ + { + "name": "List installed versions", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Byond/List", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond", + "List" + ] + } + }, + "response": [] + }, + { + "name": "Read active version", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + }, + { + "name": "Set 511.1385 active", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"version\": {\n \t\"major\": 511,\n \t\"minor\": 1385\n }\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + }, + { + "name": "Set 512.1441 active", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"version\": {\n \t\"major\": 512,\n \t\"minor\": 1441\n }\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + } + ] + }, { "name": "Login Admin Default", "request": { From 77fed81d00f558f1cb7ae8fe4085a56116ccf61e Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 30 Jul 2018 16:39:19 -0400 Subject: [PATCH 380/397] Always fixing never stopping --- .../Components/Repository/IRepositoryManager.cs | 2 +- .../Components/Repository/RepositoryManager.cs | 7 +++++-- .../Controllers/RepositoryController.cs | 8 +++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs index 92b5ad0edd..1279f7285a 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs @@ -23,7 +23,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The branch to clone /// The access string to clone from /// The for the operation - /// The newly cloned + /// The newly cloned , if one already exists Task CloneRepository(Uri url, string initialBranch, string accessString, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 25cd954457..3187d6dfe2 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -55,8 +55,6 @@ namespace Tgstation.Server.Host.Components.Repository if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false)) try { - await DeleteRepository(cancellationToken).ConfigureAwait(false); - await Task.Factory.StartNew(() => { string path = null; @@ -85,6 +83,8 @@ namespace Tgstation.Server.Host.Components.Repository catch { } throw; } + else + return null; return await LoadRepository(cancellationToken).ConfigureAwait(false); } @@ -102,7 +102,10 @@ namespace Tgstation.Server.Host.Components.Repository catch (RepositoryNotFoundException) { } }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); if (repo == null) + { + semaphore.Release(); return null; + } var localSemaphore = semaphore; return new Repository(repo, ioManager, eventConsumer, () => { diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 97afc62c66..9a703fb956 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -141,13 +141,13 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { - if (repo == null) + if (repo != null) //clone conflict return Conflict(); var job = new Models.Job { - Description = String.Format(CultureInfo.InvariantCulture, "Clone branch {1} of repository {0}", origin, cloneBranch), + Description = String.Format(CultureInfo.InvariantCulture, "Clone branch {1} of repository {0}", origin, cloneBranch ?? "master"), StartedBy = AuthenticationContext.User, CancelRightsType = RightsType.Repository, CancelRight = (int)RepositoryRights.CancelClone, @@ -219,7 +219,7 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { - if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false)) + if (repo != null && await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false)) await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); return Json(api); } @@ -283,6 +283,8 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { + if (repo == null) + return StatusCode((int)HttpStatusCode.Gone); var startSha = repo.Head; if (newTestMerges && !repo.IsGitHubRepository) From 0964c0c0147540ae210c649de65d5cc5c82091c9 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 31 Jul 2018 00:02:02 -0400 Subject: [PATCH 381/397] Endless alpha --- src/Tgstation.Server.Api/Models/Job.cs | 5 + src/Tgstation.Server.Api/Models/Repository.cs | 2 +- .../Rights/RightsHelper.cs | 13 +- .../Repository/IRepositoryManager.cs | 2 +- .../Repository/RepositoryManager.cs | 9 +- .../Controllers/ByondController.cs | 2 +- .../Controllers/DreamDaemonController.cs | 2 +- .../Controllers/DreamMakerController.cs | 2 +- .../Controllers/InstanceController.cs | 2 +- .../Controllers/JobController.cs | 4 +- .../Controllers/RepositoryController.cs | 21 +- src/Tgstation.Server.Host/Core/IJobManager.cs | 9 +- src/Tgstation.Server.Host/Core/JobHandler.cs | 5 + src/Tgstation.Server.Host/Core/JobManager.cs | 22 +- .../TgsAuthorizeAttribute.cs | 18 +- tools/TGS.postman_collection.json | 589 ++++++++++++------ v4_prototype_TODO.txt | 7 +- 17 files changed, 490 insertions(+), 224 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs index 2f413fff43..3ac1549daa 100644 --- a/src/Tgstation.Server.Api/Models/Job.cs +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -16,5 +16,10 @@ /// [Permissions(DenyWrite = true)] public User CancelledBy { get; set; } + + /// + /// Optional progress between 0 and 100 inclusive + /// + public int? Progress { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 981c77aa94..36d2c9b9a8 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Api.Models public string Origin { get; set; } /// - /// The commit HEAD points to + /// The commit HEAD points to. Not populated in responses, use instead /// [Permissions(WriteRight = RepositoryRights.SetSha)] public string Sha { get; set; } diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index ab4e16de4e..74e23dc94e 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -38,7 +38,18 @@ namespace Tgstation.Server.Api.Rights /// The /// The /// A representing the claim role name - public static string RoleName(TRight right) => String.Concat(typeof(TRight).Name, '.', right.ToString()); + public static string RoleNames(TRight right) where TRight: Enum + { + var flags = new List(); + IEnumerable GetRoleNames() + { + foreach (Enum J in Enum.GetValues(right.GetType())) + if (right.HasFlag(J)) + yield return String.Concat(typeof(TRight).Name, '.', J.ToString()); + }; + var names = GetRoleNames(); + return String.Join(",", names); + } /// /// Gets the role claim name used for a given and diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs index 1279f7285a..673384257d 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs @@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The access string to clone from /// The for the operation /// The newly cloned , if one already exists - Task CloneRepository(Uri url, string initialBranch, string accessString, CancellationToken cancellationToken); + Task CloneRepository(Uri url, string initialBranch, string accessString, Action progressReporter, CancellationToken cancellationToken); /// /// Delete the current repository diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 3187d6dfe2..f594f32132 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -49,7 +49,7 @@ namespace Tgstation.Server.Host.Components.Repository public void Dispose() => semaphore.Dispose(); /// - public async Task CloneRepository(Uri url, string initialBranch, string accessString, CancellationToken cancellationToken) + public async Task CloneRepository(Uri url, string initialBranch, string accessString, Action progressReporter, CancellationToken cancellationToken) { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false)) @@ -63,7 +63,12 @@ namespace Tgstation.Server.Host.Components.Repository path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url.ToString(), accessString), ioManager.ResolvePath("."), new CloneOptions { OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => + { + var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); + progressReporter((int)percentage); + return !cancellationToken.IsCancellationRequested; + }, RecurseSubmodules = true, OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested, RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested, diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 0a6a47f408..a137447c4e 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -100,7 +100,7 @@ namespace Tgstation.Server.Host.Controllers CancelRight = (int)ByondRights.CancelInstall, Instance = Instance }; - await jobManager.RegisterOperation(job, (paramJob, serviceProvicer, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, serviceProvicer, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false); result.InstallJob = job.ToApi(); } result.Version = byondManager.ActiveVersion; diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 323e073e00..4d805cca10 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Controllers StartedBy = AuthenticationContext.User }; await jobManager.RegisterOperation(job, - async (paramJob, serviceProvider, innerCt) => + async (paramJob, serviceProvider, progressHandler, innerCt) => { var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false); if (result == null) diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index c9acbb0d6a..e1b174c132 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -68,7 +68,7 @@ namespace Tgstation.Server.Host.Controllers CancelRight = (int)DreamMakerRights.CancelCompile, Instance = Instance }; - await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => RunCompile(paramJob, serviceProvider, Instance, ct), cancellationToken).ConfigureAwait(false); return Json(job); } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 53ac454911..9fabc92699 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -310,7 +310,7 @@ namespace Tgstation.Server.Host.Controllers StartedBy = AuthenticationContext.User }; - await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => { + await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressHandler, ct) => { try { await instanceManager.MoveInstance(Instance, rawPath, ct).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 79bb25cc15..1fee01abd5 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -71,7 +71,9 @@ namespace Tgstation.Server.Host.Controllers var job = await DatabaseContext.Jobs.Where(x => x.Id == id).Include(x => x.StartedBy).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); if (job == default(Job)) return NotFound(); - return Json(job.ToApi()); + var api = job.ToApi(); + api.Progress = jobManager.JobProgress(job); + return Json(api); } } } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 9a703fb956..a96082300a 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -80,7 +80,7 @@ namespace Tgstation.Server.Host.Controllers { CommitSha = repoSha, CompileJobs = new List(), - ActiveTestMerges = new List() //non null vals for api returns + ActiveTestMerges = new List() //non null vals for api returns }; lock (DatabaseContext) //cleaner this way @@ -95,12 +95,11 @@ namespace Tgstation.Server.Host.Controllers model.IsGitHub = repository.IsGitHubRepository; model.Origin = repository.Origin; model.Reference = repository.Reference; - model.Sha = repository.Head; //rev info stuff Models.RevisionInformation revisionInfo = null; var needsDbUpdate = await LoadRevisionInformation(repository, x => revisionInfo = x, cancellationToken).ConfigureAwait(false); - revisionInfo.OriginCommitSha = lastOriginCommitSha ?? model.Sha; + revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha ?? model.Sha; revInfoSink?.Invoke(revisionInfo); model.RevisionInformation = revisionInfo.ToApi(); return needsDbUpdate; @@ -154,9 +153,9 @@ namespace Tgstation.Server.Host.Controllers Instance = Instance }; var api = currentModel.ToApi(); - await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => + await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) => { - using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, GetAccessString(currentModel), cancellationToken).ConfigureAwait(false)) + using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, GetAccessString(currentModel), progressReporter, cancellationToken).ConfigureAwait(false)) { if (repos == null) throw new Exception("Filesystem conflict while cloning repository!"); @@ -201,7 +200,7 @@ namespace Tgstation.Server.Host.Controllers Instance = Instance }; var api = currentModel.ToApi(); - await jobManager.RegisterOperation(job, (paramJob, serviceProvider, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false); + await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false); api.ActiveJob = job.ToApi(); return Ok(); } @@ -348,12 +347,6 @@ namespace Tgstation.Server.Host.Controllers errorMessage = "P.R.E. NOT FOUND"; } - var attachedContextUser = new Models.User - { - Id = AuthenticationContext.User.Id - }; - DatabaseContext.Users.Attach(attachedContextUser); - var tm = new Models.TestMerge { Author = pr?.User.Login ?? errorMessage, @@ -362,7 +355,7 @@ namespace Tgstation.Server.Host.Controllers TitleAtMerge = pr?.Title ?? errorMessage, Comment = I.Comment, Number = I.Number, - MergedBy = attachedContextUser, + MergedBy = AuthenticationContext.User, PullRequestRevision = I.PullRequestRevision, Url = pr?.HtmlUrl ?? errorMessage }; @@ -397,7 +390,7 @@ namespace Tgstation.Server.Host.Controllers CancelRightsType = RightsType.Repository, CancelRight = (int)RepositoryRights.CancelSynchronize, }; - await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => + await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) => { using (var repos = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) if (repos != null) diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 3eabf70612..35f8d5c56e 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -11,14 +11,19 @@ namespace Tgstation.Server.Host.Core /// public interface IJobManager { + /// + /// Get the for a job + /// + int? JobProgress(Job job); + /// /// Registers a given and begins running it /// /// The - /// The operation to run taking the started , a and a + /// The operation to run taking the started , a progress reporter and a /// The for the operation /// A representing a running operation - Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken); + Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken); /// /// Cancels a give diff --git a/src/Tgstation.Server.Host/Core/JobHandler.cs b/src/Tgstation.Server.Host/Core/JobHandler.cs index ab05719268..d80bbd1322 100644 --- a/src/Tgstation.Server.Host/Core/JobHandler.cs +++ b/src/Tgstation.Server.Host/Core/JobHandler.cs @@ -32,6 +32,11 @@ namespace Tgstation.Server.Host.Core /// public void Dispose() => cancellationTokenSource.Dispose(); + /// + /// The progress of the job + /// + public int? Progress { get; set; } + /// /// Wait for to complete /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 4d8359e438..88314172f0 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -105,7 +105,7 @@ namespace Tgstation.Server.Host.Core } /// - public async Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken) + public async Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken) { using (var scope = serviceProvider.CreateScope()) { @@ -127,7 +127,14 @@ namespace Tgstation.Server.Host.Core } databaseContext.Jobs.Add(job); await databaseContext.Save(cancellationToken).ConfigureAwait(false); - var jobHandler = JobHandler.Create(x => RunJob(job, operation, x)); + var jobHandler = JobHandler.Create(x => RunJob(job, (jobParam, serviceProvider, ct) => + operation(jobParam, serviceProvider, y => + { + lock (this) + if (jobs.TryGetValue(job.Id, out var handler)) + handler.Progress = y; + }, ct), + x)); lock (this) jobs.Add(job.Id, jobHandler); } @@ -184,5 +191,16 @@ namespace Tgstation.Server.Host.Core await databaseContext.Save(cancellationToken).ConfigureAwait(false); } } + + /// + public int? JobProgress(Job job) + { + lock (this) + { + if (!jobs.TryGetValue(job.Id, out var handler)) + return null; + return handler.Progress; + } + } } } diff --git a/src/Tgstation.Server.Host/TgsAuthorizeAttribute.cs b/src/Tgstation.Server.Host/TgsAuthorizeAttribute.cs index 2281587fae..427a73b27c 100644 --- a/src/Tgstation.Server.Host/TgsAuthorizeAttribute.cs +++ b/src/Tgstation.Server.Host/TgsAuthorizeAttribute.cs @@ -19,54 +19,54 @@ namespace Tgstation.Server.Host /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(AdministrationRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(AdministrationRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(RepositoryRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(RepositoryRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ByondRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(ByondRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(DreamMakerRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(DreamMakerRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ChatSettingsRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(ChatSettingsRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ConfigurationRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(ConfigurationRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(InstanceUserRights requiredRights) => Roles = RightsHelper.RoleName(requiredRights); + public TgsAuthorizeAttribute(InstanceUserRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); } } diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index 92ea78f73b..a4153fa1d9 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -1321,14 +1321,14 @@ "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": -1,\n\t\"dreamDaemonRights\": -1,\n\t\"dreamMakerRights\": -1,\n\t\"repositoryRights\": -1,\n\t\"chatSettingsRights\": -1,\n\t\"configurationRights\": -1\n}" }, "url": { - "raw": "localhost:5000/Job/10", + "raw": "localhost:5000/Job/29", "host": [ "localhost" ], "port": "5000", "path": [ "Job", - "10" + "29" ] } }, @@ -1365,14 +1365,14 @@ "raw": "" }, "url": { - "raw": "localhost:5000/Job/11", + "raw": "localhost:5000/Job/20", "host": [ "localhost" ], "port": "5000", "path": [ "Job", - "11" + "20" ] } }, @@ -1403,6 +1403,408 @@ ], "_postman_isSubFolder": true }, + { + "name": "Byond", + "description": "", + "item": [ + { + "name": "List installed versions", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Byond/List", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond", + "List" + ] + } + }, + "response": [] + }, + { + "name": "Read active version", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + }, + { + "name": "Set 511.1385 active", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"version\": {\n \t\"major\": 511,\n \t\"minor\": 1385\n }\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + }, + { + "name": "Set 512.1441 active", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"version\": {\n \t\"major\": 512,\n \t\"minor\": 1441\n }\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + } + ], + "_postman_isSubFolder": true + }, + { + "name": "Repo", + "description": "", + "item": [ + { + "name": "Read Info", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Repository", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Repository" + ] + } + }, + "response": [] + }, + { + "name": "Just fetch", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"updateReference\": true\n}" + }, + "url": { + "raw": "localhost:5000/Repository", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Repository" + ] + } + }, + "response": [] + }, + { + "name": "Test merge some stuff", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"updateReference\": true,\n\t\"reference\": \"master\",\n\t\"newTestMerges\": [\n\t\t{\n\t\t\t\"number\": 39476,\n\t\t\t\"pullRequestRevision\": \"11edfe5\"\n\t\t},\n\t\t{\n\t\t\t\"number\": 39469,\n\t\t\t\"pullRequestRevision\": \"ee4f00d\",\n\t\t\t\"comment\": \"babby's first pr\"\n\t\t}\n\t\t]\n}" + }, + "url": { + "raw": "localhost:5000/Repository", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Repository" + ] + } + }, + "response": [] + }, + { + "name": "Clone tg", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"origin\": \"https://github.com/tgstation/tgstation\"\n}" + }, + "url": { + "raw": "localhost:5000/Repository", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Repository" + ] + } + }, + "response": [] + }, + { + "name": "Delete", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "localhost:5000/Repository", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Repository" + ] + } + }, + "response": [] + } + ], + "_postman_isSubFolder": true + }, { "name": "Online Instance ID 1", "request": { @@ -1601,185 +2003,6 @@ } ] }, - { - "name": "Byond", - "description": "", - "item": [ - { - "name": "List installed versions", - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "key": "User-Agent", - "value": "Postman/1.0" - }, - { - "key": "User-Agent", - "value": "Tgstation.Server.Api/4.0.0.0" - }, - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "InstanceId", - "value": "1" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"id\": 1,\n \"online\": false\n}" - }, - "url": { - "raw": "localhost:5000/Byond/List", - "host": [ - "localhost" - ], - "port": "5000", - "path": [ - "Byond", - "List" - ] - } - }, - "response": [] - }, - { - "name": "Read active version", - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "key": "User-Agent", - "value": "Postman/1.0" - }, - { - "key": "User-Agent", - "value": "Tgstation.Server.Api/4.0.0.0" - }, - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "InstanceId", - "value": "1" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"id\": 1,\n \"online\": false\n}" - }, - "url": { - "raw": "localhost:5000/Byond", - "host": [ - "localhost" - ], - "port": "5000", - "path": [ - "Byond" - ] - } - }, - "response": [] - }, - { - "name": "Set 511.1385 active", - "request": { - "method": "POST", - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "key": "User-Agent", - "value": "Postman/1.0" - }, - { - "key": "User-Agent", - "value": "Tgstation.Server.Api/4.0.0.0" - }, - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "InstanceId", - "value": "1" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"version\": {\n \t\"major\": 511,\n \t\"minor\": 1385\n }\n}" - }, - "url": { - "raw": "localhost:5000/Byond", - "host": [ - "localhost" - ], - "port": "5000", - "path": [ - "Byond" - ] - } - }, - "response": [] - }, - { - "name": "Set 512.1441 active", - "request": { - "method": "POST", - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "key": "User-Agent", - "value": "Postman/1.0" - }, - { - "key": "User-Agent", - "value": "Tgstation.Server.Api/4.0.0.0" - }, - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "InstanceId", - "value": "1" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"version\": {\n \t\"major\": 512,\n \t\"minor\": 1441\n }\n}" - }, - "url": { - "raw": "localhost:5000/Byond", - "host": [ - "localhost" - ], - "port": "5000", - "path": [ - "Byond" - ] - } - }, - "response": [] - } - ] - }, { "name": "Login Admin Default", "request": { diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 8262e03165..bce08e2158 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -10,13 +10,12 @@ Verify the byond cache folder location on linux Server updates. Use FOLDERS so dependencies can be properly packaged -Fix this -TgsAuthorize attribute |'ing doesn't work due to role names, change to params[] -OR use pow2 to go up and reverse tocode the enums - Don't throw arg null exceptions with null [FromBody]'s, apparently that's allowed, return bad request instead Test repo Test byond Test compile Test watchdog -test configuration \ No newline at end of file +test configuration + +Think about how to preserve the LastOriginCommit while merging/test merging From 312d88faad838ae321363cb7f602a62908167899 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 31 Jul 2018 09:42:13 -0400 Subject: [PATCH 382/397] Don't add none as a valid role you meme --- src/Tgstation.Server.Api/Rights/RightsHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs index 74e23dc94e..cbd83a7ca5 100644 --- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs +++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; namespace Tgstation.Server.Api.Rights { @@ -44,7 +45,7 @@ namespace Tgstation.Server.Api.Rights IEnumerable GetRoleNames() { foreach (Enum J in Enum.GetValues(right.GetType())) - if (right.HasFlag(J)) + if (Convert.ToInt32(J, CultureInfo.InvariantCulture) != 0 && right.HasFlag(J)) yield return String.Concat(typeof(TRight).Name, '.', J.ToString()); }; var names = GetRoleNames(); From 456aa0b84966fd740215370e0f385bec9e195ba4 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 31 Jul 2018 10:43:06 -0400 Subject: [PATCH 383/397] Jobify the entire repo update process --- .../Models/Internal/RepositorySettings.cs | 6 - src/Tgstation.Server.Api/Models/Repository.cs | 8 +- .../Rights/RepositoryRights.cs | 2 +- .../Components/Instance.cs | 2 +- .../Components/Repository/IRepository.cs | 6 +- .../Repository/IRepositoryManager.cs | 1 + .../Components/Repository/Repository.cs | 18 +- .../Controllers/RepositoryController.cs | 278 ++++++++++-------- 8 files changed, 181 insertions(+), 140 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index d75df89312..14dac247ab 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -9,12 +9,6 @@ namespace Tgstation.Server.Api.Models.Internal [Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)] public class RepositorySettings { - /// - /// The last commit recognized from the origin remote - /// - [Permissions(DenyWrite = true)] - public string LastOriginCommitSha { get; set; } - /// /// The name of the committer /// diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 36d2c9b9a8..0970a9655c 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -15,10 +15,10 @@ namespace Tgstation.Server.Api.Models public string Origin { get; set; } /// - /// The commit HEAD points to. Not populated in responses, use instead + /// The commit HEAD should point to. Not populated in responses, use instead for retrieval /// [Permissions(WriteRight = RepositoryRights.SetSha)] - public string Sha { get; set; } + public string CheckoutSha { get; set; } /// /// The current for the @@ -39,10 +39,10 @@ namespace Tgstation.Server.Api.Models public Job ActiveJob { get; set; } /// - /// Do the equivalent of a git pull. Will attempt to merge unless is also specified + /// Do the equivalent of a git pull. Will attempt to merge unless is also specified in which case a hard reset will be performed after checking out /// [Permissions(WriteRight = RepositoryRights.UpdateBranch)] - public bool? UpdateReference { get; set; } + public bool? UpdateFromOrigin { get; set; } /// /// The branch or tag HEAD points to diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index 5eb33c11b7..6c86be2419 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -63,6 +63,6 @@ namespace Tgstation.Server.Api.Rights /// /// User may cancel synchronize operations /// - CancelSynchronize = 8192 + CancelPendingChanges = 8192 } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 6d16695529..259da44cc1 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -150,7 +150,7 @@ namespace Tgstation.Server.Host.Components } if (repo == null) continue; - await repo.FetchOrigin(accessToken, cancellationToken).ConfigureAwait(false); + await repo.FetchOrigin(accessToken, null, cancellationToken).ConfigureAwait(false); await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); var job = await DreamMaker.Compile(projectName, timeout, repo, cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 23b6d68b47..a2821a2eb9 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -61,16 +61,18 @@ namespace Tgstation.Server.Host.Components.Repository /// The e-mail of the merge committer /// The access string to fetch from the origin repository /// The for the operation + /// Optional function to report 0-100 progress of the clone /// A resulting in the SHA of the new HEAD on success, on merge conflict - Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, CancellationToken cancellationToken); + Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action progressReporter, CancellationToken cancellationToken); /// /// Fetch commits from the origin repository /// /// The access string to fetch from the origin repository + /// Optional function to report 0-100 progress of the clone /// The for the operation /// A representing the running operation - Task FetchOrigin(string accessString, CancellationToken cancellationToken); + Task FetchOrigin(string accessString, Action progressReporter, CancellationToken cancellationToken); /// /// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs index 673384257d..08c6a8cd60 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs @@ -22,6 +22,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The of the remote repository to clone /// The branch to clone /// The access string to clone from + /// A function to report 0-100 progress of the clone /// The for the operation /// The newly cloned , if one already exists Task CloneRepository(Uri url, string initialBranch, string accessString, Action progressReporter, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 11e516ee2a..39552fa2c6 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -139,7 +139,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, CancellationToken cancellationToken) + public async Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action progressReporter, CancellationToken cancellationToken) { if (!IsGitHubRepository) @@ -164,7 +164,12 @@ namespace Tgstation.Server.Host.Components.Repository { Prune = true, OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => + { + var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); + progressReporter?.Invoke((int)percentage); + return !cancellationToken.IsCancellationRequested; + }, OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested }, logMessage); } @@ -217,7 +222,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public Task FetchOrigin(string accessString, CancellationToken cancellationToken) => Task.WhenAll( + public Task FetchOrigin(string accessString, Action progressReporter, CancellationToken cancellationToken) => Task.WhenAll( eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty(), cancellationToken), Task.Factory.StartNew(() => { @@ -228,7 +233,12 @@ namespace Tgstation.Server.Host.Components.Repository { Prune = true, OnProgress = (a) => !cancellationToken.IsCancellationRequested, - OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, + OnTransferProgress = (a) => + { + var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2)); + progressReporter?.Invoke((int)percentage); + return !cancellationToken.IsCancellationRequested; + }, OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested }, "Fetch origin commits"); } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index a96082300a..8f7346534b 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Controllers static string GetAccessString(Api.Models.Internal.RepositorySettings repositorySettings) => repositorySettings.AccessUser != null ? String.Concat(repositorySettings.AccessUser, '@', repositorySettings.AccessToken) : null; - async Task LoadRevisionInformation(Components.Repository.IRepository repository, Action revInfoSink, CancellationToken cancellationToken) + async Task LoadRevisionInformation(Components.Repository.IRepository repository, string lastOriginCommitSha, Action revInfoSink, CancellationToken cancellationToken) { var repoSha = repository.Head; var revisionInfo = await DatabaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha) @@ -86,6 +86,7 @@ namespace Tgstation.Server.Host.Controllers lock (DatabaseContext) //cleaner this way DatabaseContext.RevisionInformations.Add(revisionInfo); } + revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha ?? repository.Head; revInfoSink?.Invoke(revisionInfo); return needsDbUpdate; } @@ -98,10 +99,9 @@ namespace Tgstation.Server.Host.Controllers //rev info stuff Models.RevisionInformation revisionInfo = null; - var needsDbUpdate = await LoadRevisionInformation(repository, x => revisionInfo = x, cancellationToken).ConfigureAwait(false); - revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha ?? model.Sha; - revInfoSink?.Invoke(revisionInfo); + var needsDbUpdate = await LoadRevisionInformation(repository, lastOriginCommitSha, x => revisionInfo = x, cancellationToken).ConfigureAwait(false); model.RevisionInformation = revisionInfo.ToApi(); + revInfoSink?.Invoke(revisionInfo); return needsDbUpdate; } @@ -184,8 +184,7 @@ namespace Tgstation.Server.Host.Controllers if (currentModel == default) return StatusCode((int)HttpStatusCode.Gone); - - currentModel.LastOriginCommitSha = null; + currentModel.AccessToken = null; currentModel.AccessUser = null; @@ -218,7 +217,8 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { - if (repo != null && await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false)) + if (repo != null && await PopulateApi(api, repo, null, null, cancellationToken).ConfigureAwait(false)) + //user may have fucked with the repo without telling us, do what we can await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); return Json(api); } @@ -234,10 +234,10 @@ namespace Tgstation.Server.Host.Controllers if (model.AccessUser == null ^ model.AccessToken == null) return BadRequest(new { message = "Either both accessToken and accessUser must be present or neither!" }); - if (model.Sha != null && model.Reference != null) + if (model.CheckoutSha != null && model.Reference != null) return BadRequest(new { message = "Only one of sha or reference may be specified!" }); - if(model.Sha != null && model.UpdateReference == true) + if (model.CheckoutSha != null && model.UpdateFromOrigin == true) return BadRequest(new { message = "Cannot update a reference when checking out a sha!" }); if (model.Origin != null) @@ -275,133 +275,167 @@ namespace Tgstation.Server.Host.Controllers || CheckModified(x => x.CommitterEmail, RepositoryRights.ChangeCommitter) || CheckModified(x => x.CommitterName, RepositoryRights.ChangeCommitter) || CheckModified(x => x.PushTestMergeCommits, RepositoryRights.ChangeTestMergeCommits) - || CheckModified(x => x.ShowTestMergeCommitters, RepositoryRights.ChangeTestMergeCommits)) + || CheckModified(x => x.ShowTestMergeCommitters, RepositoryRights.ChangeTestMergeCommits) + || (model.UpdateFromOrigin == true && !userRights.HasFlag(RepositoryRights.UpdateBranch))) return Forbid(); - //so that's the stuff that is just db, the rest is tricky + //no, just one save changes at the end for sanity + //await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + var job = new Models.Job { - if (repo == null) - return StatusCode((int)HttpStatusCode.Gone); - var startSha = repo.Head; + Description = "Apply repository changes", + StartedBy = AuthenticationContext.User, + Instance = Instance, + CancelRightsType = RightsType.Repository, + CancelRight = (int)RepositoryRights.CancelPendingChanges, + }; - if (newTestMerges && !repo.IsGitHubRepository) - return Conflict(new { message = "Cannot test merge on a non GitHub based repository!" }); - - var committerName = currentModel.ShowTestMergeCommitters.Value ? AuthenticationContext.User.Name : currentModel.CommitterName; - var accessString = GetAccessString(currentModel); - if (model.UpdateReference == true) + await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) => + { + using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { - if (!repo.Tracking && model.Reference == null) - return Conflict(new { message = "Not on an updatable reference!" }); - if (!userRights.HasFlag(RepositoryRights.UpdateBranch)) - return Forbid(); - await repo.FetchOrigin(accessString, cancellationToken).ConfigureAwait(false); - if (model.Sha == null && model.Reference == null) - await repo.MergeOrigin(committerName, currentModel.CommitterEmail, cancellationToken).ConfigureAwait(false); - } + if (repo == null) + throw new InvalidOperationException("Repository could not be loaded!"); - if (model.Sha != null || model.Reference != null) - { - await repo.CheckoutObject(model.Sha ?? model.Reference, cancellationToken).ConfigureAwait(false); + var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null; - if (model.UpdateReference == true && model.Reference != null) + var startSha = repo.Head; + + if (newTestMerges && !repo.IsGitHubRepository) + throw new InvalidOperationException("Cannot test merge on a non GitHub based repository!"); + + var committerName = currentModel.ShowTestMergeCommitters.Value ? AuthenticationContext.User.Name : currentModel.CommitterName; + var accessString = GetAccessString(currentModel); + + var numFetches = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0); + var doneFetches = 0; + if (numFetches > 0) + progressReporter(0); + + //get a base line for where we are + Models.RevisionInformation lastRevisionInfo = null; + await LoadRevisionInformation(repo, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false); + + //apply new rev info, tracking applied test merges + async Task UpdateRevInfo() { - if (!repo.Tracking) - return Conflict(new { message = "Checked out reference is does not track an object!" }); - await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); - } - } - - //now add any testmerges - if (newTestMerges) - { - var repoOwner = repo.GitHubOwner; - var repoName = repo.GitHubRepoName; - var allAddedTestMerges = new List(); - foreach (var I in model.NewTestMerges) - { - var prTask = gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number); - - await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, accessString, cancellationToken).ConfigureAwait(false); - - Octokit.PullRequest pr = null; - string errorMessage = null; - - Models.RevisionInformation revisionInformation = null; - var revInfoTask = LoadRevisionInformation(repo, x => revisionInformation = x, cancellationToken); - - try - { - pr = await prTask.ConfigureAwait(false); - } - catch (Octokit.RateLimitExceededException) - { - //you look at your anonymous access and sigh - errorMessage = "P.R.E. RATE LIMITED"; - } - catch (Octokit.NotFoundException) - { - //you look at your shithub access and sigh - errorMessage = "P.R.E. NOT FOUND"; - } - - var tm = new Models.TestMerge - { - Author = pr?.User.Login ?? errorMessage, - BodyAtMerge = pr?.Body, - MergedAt = DateTimeOffset.Now, - TitleAtMerge = pr?.Title ?? errorMessage, - Comment = I.Comment, - Number = I.Number, - MergedBy = AuthenticationContext.User, - PullRequestRevision = I.PullRequestRevision, - Url = pr?.HtmlUrl ?? errorMessage - }; - if (pr == null) - tm.BodyAtMerge = errorMessage; - - allAddedTestMerges.Add(tm); - - await revInfoTask.ConfigureAwait(false); - - revisionInformation.PrimaryTestMerge = tm; - revisionInformation.ActiveTestMerges.AddRange(allAddedTestMerges.Select(x => new RevInfoTestMerge - { - TestMerge = x - })); - } - } - - var api = currentModel.ToApi(); - if (await PopulateApi(api, repo, currentModel.LastOriginCommitSha, null, cancellationToken).ConfigureAwait(false) || newTestMerges) - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - - //synchronize now because we don't care if we fail so its safe to fire/forget the job - - if (startSha != repo.Head) - { - var job = new Models.Job - { - Description = "Synchronize repository changes", - StartedBy = AuthenticationContext.User, - Instance = Instance, - CancelRightsType = RightsType.Repository, - CancelRight = (int)RepositoryRights.CancelSynchronize, + var last = lastRevisionInfo; + await LoadRevisionInformation(repo, last.OriginCommitSha, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false); + lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges); }; - await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) => + + try { - using (var repos = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) - if (repos != null) - await repos.Sychronize(accessString, ct).ConfigureAwait(false); - }, cancellationToken).ConfigureAwait(false); + //fetch/pull + if (model.UpdateFromOrigin == true) + { + if (!repo.Tracking && model.Reference == null) + throw new InvalidOperationException("Not on an updatable reference!"); + await repo.FetchOrigin(accessString, x => progressReporter(x / numFetches), cancellationToken).ConfigureAwait(false); + doneFetches = 1; + if (!modelHasShaOrReference) + { + await repo.MergeOrigin(committerName, currentModel.CommitterEmail, cancellationToken).ConfigureAwait(false); + await UpdateRevInfo().ConfigureAwait(false); + } + } - api.ActiveJob = job.ToApi(); + //checkout/hard reset + if (modelHasShaOrReference) + { + if ((model.CheckoutSha != null && repo.Head.ToUpperInvariant() != model.CheckoutSha.ToUpperInvariant()) + || (model.Reference != null && repo.Reference != model.Reference)) + { + await repo.CheckoutObject(model.CheckoutSha ?? model.Reference, cancellationToken).ConfigureAwait(false); + await LoadRevisionInformation(repo, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false); //we've either seen origin before or what we're checking out is on origin + } + + if (model.UpdateFromOrigin == true && model.Reference != null) + { + if (!repo.Tracking) + throw new InvalidOperationException("Checked out reference does not track a remote object!"); + await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); + await LoadRevisionInformation(repo, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false); + //repo head is on origin so force this + //will update the db if necessary + lastRevisionInfo.OriginCommitSha = repo.Head; + } + } + + //test merging + if (newTestMerges) + { + var repoOwner = repo.GitHubOwner; + var repoName = repo.GitHubRepoName; + foreach (var I in model.NewTestMerges) + { + var prTask = gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number); + + await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, accessString, x => progressReporter((x + 100 * doneFetches) / numFetches), cancellationToken).ConfigureAwait(false); + ++doneFetches; + + var revInfoUpdateTask = UpdateRevInfo(); + + Octokit.PullRequest pr = null; + string errorMessage = null; + try + { + pr = await prTask.ConfigureAwait(false); + } + catch (Octokit.RateLimitExceededException) + { + //you look at your anonymous access and sigh + errorMessage = "P.R.E. RATE LIMITED"; + } + catch (Octokit.NotFoundException) + { + //you look at your shithub access and sigh + errorMessage = "P.R.E. NOT FOUND"; + } + + var tm = new Models.TestMerge + { + Author = pr?.User.Login ?? errorMessage, + BodyAtMerge = pr?.Body, + MergedAt = DateTimeOffset.Now, + TitleAtMerge = pr?.Title ?? errorMessage, + Comment = I.Comment, + Number = I.Number, + MergedBy = AuthenticationContext.User, + PullRequestRevision = I.PullRequestRevision, + Url = pr?.HtmlUrl ?? errorMessage + }; + if (pr == null) + tm.BodyAtMerge = errorMessage; + + await revInfoUpdateTask.ConfigureAwait(false); + + lastRevisionInfo.PrimaryTestMerge = tm; + lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge + { + TestMerge = tm + }); + } + } + + if (startSha != repo.Head) + { + await repo.Sychronize(accessString, ct).ConfigureAwait(false); + await UpdateRevInfo().ConfigureAwait(false); + } + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + } + catch + { + //the stuff didn't make it into the db, forget what we've done and abort + await repo.CheckoutObject(startSha, default).ConfigureAwait(false); + throw; + } } + }, cancellationToken).ConfigureAwait(false); - return Json(api); - } + return Json(job.ToApi()); } } } From 5aba26e0dc9747be024d686c414bc42b524193cd Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 31 Jul 2018 11:07:21 -0400 Subject: [PATCH 384/397] Other fixups --- src/Tgstation.Server.Api/Models/Repository.cs | 2 +- .../Components/Repository/IRepository.cs | 10 +++++----- .../Components/Repository/Repository.cs | 15 +++++++-------- .../Controllers/RepositoryController.cs | 12 ++++++++++-- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs index 0970a9655c..9b65efa513 100644 --- a/src/Tgstation.Server.Api/Models/Repository.cs +++ b/src/Tgstation.Server.Api/Models/Repository.cs @@ -51,7 +51,7 @@ namespace Tgstation.Server.Api.Models public string Reference { get; set; } /// - /// for new s + /// for new s. Note that merges that conflict will not be performed /// [Permissions(WriteRight = RepositoryRights.MergePullRequest)] public List NewTestMerges { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index a2821a2eb9..13dde17437 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -62,8 +62,8 @@ namespace Tgstation.Server.Host.Components.Repository /// The access string to fetch from the origin repository /// The for the operation /// Optional function to report 0-100 progress of the clone - /// A resulting in the SHA of the new HEAD on success, on merge conflict - Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action progressReporter, CancellationToken cancellationToken); + /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict + Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action progressReporter, CancellationToken cancellationToken); /// /// Fetch commits from the origin repository @@ -79,7 +79,7 @@ namespace Tgstation.Server.Host.Components.Repository /// /// The for the operation /// A resulting in the SHA of the new HEAD - Task ResetToOrigin(CancellationToken cancellationToken); + Task ResetToOrigin(CancellationToken cancellationToken); /// /// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository @@ -87,8 +87,8 @@ namespace Tgstation.Server.Host.Components.Repository /// The name of the merge committer /// The e-mail of the merge committer /// The for the operation - /// A resulting in the SHA of the new HEAD. if the merge resulted in conflict - Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken); + /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict + Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken); /// /// Force push the current repository HEAD to ; diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 39552fa2c6..9dbab666f3 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -139,7 +139,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action progressReporter, CancellationToken cancellationToken) + public async Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action progressReporter, CancellationToken cancellationToken) { if (!IsGitHubRepository) @@ -206,10 +206,10 @@ namespace Tgstation.Server.Host.Components.Repository if (result.Status == MergeStatus.Conflicts) { await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { originalCommit.Tip.Sha, targetCommit, originalCommit.FriendlyName ?? UnknownReference, prBranchName }, cancellationToken).ConfigureAwait(false); - return null; + return false; } - return result.Commit.Sha; + return true; } /// @@ -285,13 +285,13 @@ namespace Tgstation.Server.Host.Components.Repository }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// - public async Task ResetToOrigin(CancellationToken cancellationToken) + public async Task ResetToOrigin(CancellationToken cancellationToken) { if (!repository.Head.IsTracking) throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!"); var trackedBranch = repository.Head.TrackedBranch; await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false); - return await Task.Factory.StartNew(() => + await Task.Factory.StartNew(() => { Commands.Checkout((LibGit2Sharp.Repository)repository, repository.Head.TrackedBranch, new CheckoutOptions { @@ -299,7 +299,6 @@ namespace Tgstation.Server.Host.Components.Repository }); cancellationToken.ThrowIfCancellationRequested(); repository.RemoveUntrackedFiles(); - return trackedBranch.Tip.Sha; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); } @@ -312,7 +311,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken) + public async Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken) { MergeResult result = null; Branch trackedBranch = null; @@ -350,7 +349,7 @@ namespace Tgstation.Server.Host.Components.Repository return null; } - return Head; + return result.Status != MergeStatus.NonFastForward; } /// diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 8f7346534b..f05938f75a 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -336,8 +336,12 @@ namespace Tgstation.Server.Host.Controllers doneFetches = 1; if (!modelHasShaOrReference) { - await repo.MergeOrigin(committerName, currentModel.CommitterEmail, cancellationToken).ConfigureAwait(false); + var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, cancellationToken).ConfigureAwait(false); + if (!fastForward.HasValue) + throw new InvalidOperationException("Merge conflict occurred during origin update!"); await UpdateRevInfo().ConfigureAwait(false); + if (fastForward.Value) + lastRevisionInfo.OriginCommitSha = repo.Head; } } @@ -372,7 +376,11 @@ namespace Tgstation.Server.Host.Controllers { var prTask = gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number); - await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, accessString, x => progressReporter((x + 100 * doneFetches) / numFetches), cancellationToken).ConfigureAwait(false); + var mergeResult = await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, accessString, x => progressReporter((x + 100 * doneFetches) / numFetches), cancellationToken).ConfigureAwait(false); + + if (!mergeResult.HasValue) //conflict, we don't care, dd already knows + continue; + ++doneFetches; var revInfoUpdateTask = UpdateRevInfo(); From 6fe456e5844287708e22d9842fc966a8b09b805b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 31 Jul 2018 17:28:45 -0400 Subject: [PATCH 385/397] I give up --- appveyor.yml | 11 ++-- .../AbsoluteCompilationAssemblyResolver.cs | 47 ++++++++++++++ .../IsolatedAssemblyContextFactory.cs | 5 +- .../IsolatedServerFactory.cs | 65 ++++++++++++++++++- .../Tgstation.Server.Host.Watchdog.csproj | 17 ++++- .../Watchdog.cs | 9 ++- .../appsettings.Docker.json | 0 .../appsettings.json | 0 src/Tgstation.Server.Host/Core/Application.cs | 6 +- .../SqliteDesignTimeDbContextFactory.cs | 3 +- .../Models/SqliteDatabaseContext.cs | 22 ++++++- .../Tgstation.Server.Host.csproj | 9 --- tgstation-server.sln | 16 ++--- v4_prototype_TODO.txt | 2 + 14 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 src/Tgstation.Server.Host.Watchdog/AbsoluteCompilationAssemblyResolver.cs rename src/{Tgstation.Server.Host => Tgstation.Server.Host.Watchdog}/appsettings.Docker.json (100%) rename src/{Tgstation.Server.Host => Tgstation.Server.Host.Watchdog}/appsettings.json (100%) diff --git a/appveyor.yml b/appveyor.yml index 539f2fb6d2..b049feee57 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -57,17 +57,14 @@ test_script: after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 + #host updater - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION% + #console - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% + #service - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse - - ps: Remove-Item artifacts/ServerHost/appsettings.Docker.json - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerService/lib/Default -recurse - #I have literally no idea how these jsons got here - - ps: Remove-Item -path artifacts/ServerService/appsettings.json - - ps: Remove-Item -path artifacts/ServerService/appsettings.Docker.json - - ps: Move-Item -path artifacts/ServerService/lib/Default/appsettings.json -destination artifacts/ServerService/ - - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerConsole/lib/Default -recurse - - ps: Move-Item -path artifacts/ServerConsole/lib/Default/appsettings.json -destination artifacts/ServerConsole/ + #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." }}} diff --git a/src/Tgstation.Server.Host.Watchdog/AbsoluteCompilationAssemblyResolver.cs b/src/Tgstation.Server.Host.Watchdog/AbsoluteCompilationAssemblyResolver.cs new file mode 100644 index 0000000000..a687111c28 --- /dev/null +++ b/src/Tgstation.Server.Host.Watchdog/AbsoluteCompilationAssemblyResolver.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.DependencyModel.Resolution; + +namespace Tgstation.Server.Host.Watchdog +{ + /// + /// for an absolute + /// + sealed class AbsoluteCompilationAssemblyResolver : ICompilationAssemblyResolver + { + /// + /// The base path + /// + readonly string basePath; + + /// + /// Construct a + /// + /// The value of + public AbsoluteCompilationAssemblyResolver(string basePath) + { + this.basePath = basePath ?? throw new ArgumentNullException(nameof(basePath)); + } + + /// + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + var paths = new List(); + + foreach (var assembly in library.Assemblies) + { + var assemblyFile = Path.Combine(basePath, assembly); + if (File.Exists(assemblyFile)) + paths.Add(assemblyFile); + else + return false; + } + + // only modify the assemblies parameter if we've resolved all files + assemblies?.AddRange(paths); + return true; + } + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs index 6f97144265..cfa1c05cc3 100644 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Host.Startup; +using Microsoft.DotNet.PlatformAbstractions; +using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host.Watchdog { @@ -6,6 +7,6 @@ namespace Tgstation.Server.Host.Watchdog sealed class IsolatedAssemblyContextFactory : IIsolatedAssemblyContextFactory { /// - public IServerFactory CreateIsolatedServerFactory(string assemblyPath) => new IsolatedServerFactory(assemblyPath); + public IServerFactory CreateIsolatedServerFactory(string assemblyPath) => new IsolatedServerFactory(assemblyPath, RuntimeEnvironment.GetRuntimeIdentifier()); } } diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs index 14bae38479..89c9e5ed38 100644 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs @@ -1,7 +1,11 @@ -using System; +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.DependencyModel.Resolution; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Loader; using Tgstation.Server.Host.Startup; @@ -21,23 +25,62 @@ namespace Tgstation.Server.Host.Watchdog /// readonly string assemblyPath; + /// + /// The runtime identifier to load dependencies from + /// + readonly string runtimeIdentifier; + + /// + /// The of the being loaded + /// + DependencyContext dependencyContext; + + /// + /// The for the + /// + ICompilationAssemblyResolver compilationAssemblyResolver; + /// /// Construct a /// /// The value of - public IsolatedServerFactory(string assemblyPath) + /// The value of + public IsolatedServerFactory(string assemblyPath, string runtimeIdentifier) { this.assemblyPath = assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath)); + this.runtimeIdentifier = runtimeIdentifier ?? throw new ArgumentNullException(nameof(runtimeIdentifier)); Resolving += IsolatedServerFactory_Resolving; } //https://stackoverflow.com/a/40921746/3976486 + //https://samcragg.wordpress.com/2017/06/30/resolving-assemblies-in-net-core/ Assembly IsolatedServerFactory_Resolving(AssemblyLoadContext context, AssemblyName assemblyName) { if (assemblyName.Name.EndsWith("resources", StringComparison.Ordinal)) return null; + + var library = dependencyContext.RuntimeLibraries.FirstOrDefault(x => String.Equals(x.Name, assemblyName.Name, StringComparison.OrdinalIgnoreCase)); + if (library != null) + { + var wrapper = new CompilationLibrary( + library.Type, + library.Name, + library.Version, + library.Hash, + library.RuntimeAssemblyGroups.SelectMany(x => x.AssetPaths), + library.Dependencies, + library.Serviceable + ); + + var assemblies = new List(); + compilationAssemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies); + if (assemblies.Count > 0) + return context.LoadFromAssemblyPath(assemblies.First()); + } + + //could be not a "true" dependency i.e. CodeAnaylsis var foundDll = Directory.GetFileSystemEntries(assemblyPath, String.Join(".", assemblyName.Name, DllExtension), SearchOption.AllDirectories).FirstOrDefault(); if (foundDll != default) return context.LoadFromAssemblyPath(foundDll); @@ -53,9 +96,27 @@ namespace Tgstation.Server.Host.Watchdog public IServer CreateServer(string[] args, string updatePath) { var assembly = LoadFromAssemblyPath(Path.Combine(assemblyPath, assemblyFileName)); + dependencyContext = DependencyContext.Load(assembly); + + var resolvers = new List(); + + var fallbacks = dependencyContext.RuntimeGraph.Where(x => x.Runtime == runtimeIdentifier).FirstOrDefault(); + + if(fallbacks != default) + { + var runtimesDirectory = Path.Combine(assemblyPath, "runtimes"); + resolvers.Add(new AbsoluteCompilationAssemblyResolver(Path.Combine(runtimesDirectory, runtimeIdentifier))); + resolvers.AddRange(fallbacks.Fallbacks.Select(x => new AbsoluteCompilationAssemblyResolver(Path.Combine(runtimesDirectory, x)))); + } + + resolvers.Add(new AppBaseCompilationAssemblyResolver(assemblyPath)); + + compilationAssemblyResolver = new CompositeCompilationAssemblyResolver(resolvers.ToArray()); + //find the IServerFactory implementation var serverFactoryInterfaceType = typeof(IServerFactory); + var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index 138e10d50f..e3764f0e2b 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -17,7 +17,8 @@ - + + @@ -26,4 +27,18 @@ + + + PreserveNewest + + + + + + + + + + + diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 6ef0b74b74..bd7cfe82ae 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -43,11 +43,18 @@ namespace Tgstation.Server.Host.Watchdog { const string DefaultAssemblyPath = "Default"; - var assemblyStoragePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "lib"); + var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + +#if DEBUG + rootLocation = Path.GetFullPath("../../../../Tgstation.Server.Host.Watchdog/bin/Debug/netstandard2.0"); +#endif + + var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog logger.LogInformation("Host watchdog starting..."); var nextAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, DefaultAssemblyPath)); + string lastAssemblyPath = null; try { diff --git a/src/Tgstation.Server.Host/appsettings.Docker.json b/src/Tgstation.Server.Host.Watchdog/appsettings.Docker.json similarity index 100% rename from src/Tgstation.Server.Host/appsettings.Docker.json rename to src/Tgstation.Server.Host.Watchdog/appsettings.Docker.json diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host.Watchdog/appsettings.json similarity index 100% rename from src/Tgstation.Server.Host/appsettings.json rename to src/Tgstation.Server.Host.Watchdog/appsettings.json diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 7ef52e874c..b1108ed177 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -101,13 +101,14 @@ namespace Tgstation.Server.Host.Core var generalConfigurationSection = configuration.GetSection(GeneralConfiguration.Section); services.Configure(generalConfigurationSection); + //remember, anything you .Get manually can be null if the config is missing var generalConfiguration = generalConfigurationSection.Get(); var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var ioManager = new DefaultIOManager(); - if (!generalConfiguration.DisableFileLogging) + if (generalConfiguration?.DisableFileLogging != true) { - var logPath = !String.IsNullOrEmpty(generalConfiguration.LogFileDirectory) ? generalConfiguration.LogFileDirectory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs"); + var logPath = !String.IsNullOrEmpty(generalConfiguration?.LogFileDirectory) ? generalConfiguration.LogFileDirectory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs"); services.AddLogging(builder => builder.AddFile(ioManager.ConcatPath(logPath, "tgs-{Date}.log"))); } @@ -151,6 +152,7 @@ namespace Tgstation.Server.Host.Core }); var databaseConfiguration = databaseConfigurationSection.Get(); + void ConfigureDatabase(DbContextOptionsBuilder builder) { if (hostingEnvironment.IsDevelopment()) diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs index ece917a6b1..bf8b3c379b 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Logging; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Models.Migrations @@ -9,6 +10,6 @@ namespace Tgstation.Server.Host.Models.Migrations sealed class SqliteDesignTimeDbContextFactory : IDesignTimeDbContextFactory { /// - public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher()))); + public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher())), new LoggerFactory().CreateLogger()); } } diff --git a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs index 57885f1e56..828012b05b 100644 --- a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs @@ -1,5 +1,7 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using System; using Tgstation.Server.Host.Configuration; namespace Tgstation.Server.Host.Models @@ -9,20 +11,34 @@ namespace Tgstation.Server.Host.Models /// sealed class SqliteDatabaseContext : DatabaseContext { + /// + /// The for the + /// + readonly ILogger logger; + /// /// Construct a /// /// The for the /// The of for the /// The for the - public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, databaseSeeder) - { } + public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } /// protected override void OnConfiguring(DbContextOptionsBuilder options) { base.OnConfiguring(options); - options.UseSqlite(ConnectionString); + //on the off chance that connection string is null here we default to a db file next to the executable since this is the default database + if (ConnectionString == null) + { + logger.LogWarning("No database configured! Defaulting to SQLite in the working directory!"); + options.UseSqlite("Data Source=TgsDatabase.db3"); + } + else + options.UseSqlite(ConnectionString); } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 3643b12614..4a5ae909e1 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -24,15 +24,6 @@ - - - PreserveNewest - - - PreserveNewest - - - diff --git a/tgstation-server.sln b/tgstation-server.sln index 3a752ae3e9..1ae18e7168 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -103,13 +103,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v4", "v4", "{057FAC33-CC31- src\DMAPI\tgs\v4\commands.dm = src\DMAPI\tgs\v4\commands.dm EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Console.Debug", "src\Tgstation.Server.Host.Console.Debug\Tgstation.Server.Host.Console.Debug.csproj", "{15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{A55C1117-5808-4AB2-BEA6-4D4A3E66A2F2}" ProjectSection(SolutionItems) = preProject tools\TGS.postman_collection.json = tools\TGS.postman_collection.json EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Console.Debug", "src\Tgstation.Server.Host.Console.Debug\Tgstation.Server.Host.Console.Debug.csproj", "{D32A005E-D920-4E79-9EB9-8671ABC0E2E3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -219,12 +219,12 @@ Global {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.Build.0 = Release|Any CPU - {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Docker|Any CPU.ActiveCfg = Debug|Any CPU - {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Docker|Any CPU.Build.0 = Debug|Any CPU - {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15BE5763-DA6F-433F-A4C2-9FCF85F5CC21}.Release|Any CPU.Build.0 = Release|Any CPU + {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Docker|Any CPU.ActiveCfg = Debug|Any CPU + {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Docker|Any CPU.Build.0 = Debug|Any CPU + {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index bce08e2158..d44c7646f5 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -19,3 +19,5 @@ Test watchdog test configuration Think about how to preserve the LastOriginCommit while merging/test merging + +Rewrite the autoupdater to use a process \ No newline at end of file From 2722b6267390c953fe9ceb776d20a780897c3ebb Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 13:29:04 -0400 Subject: [PATCH 386/397] Mostly done move to watchdog as seperate process. Need to just test updating --- appveyor.yml | 4 +- .../Program.cs | 16 -- ...Tgstation.Server.Host.Console.Debug.csproj | 20 -- src/Tgstation.Server.Host.Console/Program.cs | 14 +- .../Tgstation.Server.Host.Startup.csproj | 20 -- .../AbsoluteCompilationAssemblyResolver.cs | 47 ----- .../IActiveLibraryDeleter.cs | 14 -- .../IIsolatedAssemblyContextFactory.cs | 17 -- .../IsolatedAssemblyContextFactory.cs | 12 -- .../IsolatedServerFactory.cs | 131 ------------ .../NativeMethods.cs | 21 -- .../PosixActiveLibraryDeleter.cs | 14 -- .../Tgstation.Server.Host.Watchdog.csproj | 8 +- .../Watchdog.cs | 196 ++++++++++++++---- .../WatchdogFactory.cs | 2 +- .../WindowsActiveLibraryDeleter.cs | 36 ---- .../Chat/Providers/DiscordProvider.cs | 2 +- .../Components/Repository/IRepository.cs | 4 +- .../Components/Repository/Repository.cs | 2 +- .../Controllers/AdministrationController.cs | 7 +- .../Controllers/RepositoryController.cs | 2 +- .../Core/IServerUpdater.cs | 6 +- .../IServer.cs | 7 +- .../IServerFactory.cs | 2 +- .../Models/SqliteDatabaseContext.cs | 1 + src/Tgstation.Server.Host/Program.cs | 66 ++++++ src/Tgstation.Server.Host/Server.cs | 56 +++-- src/Tgstation.Server.Host/ServerFactory.cs | 3 +- .../Tgstation.Server.Host.csproj | 17 +- .../Core/TestApplication.cs | 2 +- .../TestWatchdog.cs | 9 +- tgstation-server.sln | 16 -- 32 files changed, 307 insertions(+), 467 deletions(-) delete mode 100644 src/Tgstation.Server.Host.Console.Debug/Program.cs delete mode 100644 src/Tgstation.Server.Host.Console.Debug/Tgstation.Server.Host.Console.Debug.csproj delete mode 100644 src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj delete mode 100644 src/Tgstation.Server.Host.Watchdog/AbsoluteCompilationAssemblyResolver.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/IActiveLibraryDeleter.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/IIsolatedAssemblyContextFactory.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/NativeMethods.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/PosixActiveLibraryDeleter.cs delete mode 100644 src/Tgstation.Server.Host.Watchdog/WindowsActiveLibraryDeleter.cs rename src/{Tgstation.Server.Host.Startup => Tgstation.Server.Host}/IServer.cs (66%) rename src/{Tgstation.Server.Host.Startup => Tgstation.Server.Host}/IServerFactory.cs (92%) create mode 100644 src/Tgstation.Server.Host/Program.cs diff --git a/appveyor.yml b/appveyor.yml index b049feee57..f331baffb1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -58,9 +58,9 @@ after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 #host updater - - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION% + - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION% --no-build #console - - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% + - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% --no-build #service - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerService/lib/Default -recurse diff --git a/src/Tgstation.Server.Host.Console.Debug/Program.cs b/src/Tgstation.Server.Host.Console.Debug/Program.cs deleted file mode 100644 index 231ff0654a..0000000000 --- a/src/Tgstation.Server.Host.Console.Debug/Program.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; -using Tgstation.Server.Host.Startup; - -namespace Tgstation.Server.Host.Console.Debug -{ - static class Program - { - internal static IServerFactory serverFactory = new ServerFactory(); - - static async Task Main(string[] args) - { - using (var server = serverFactory.CreateServer(args, "Updates")) - await server.RunAsync(default).ConfigureAwait(false); - } - } -} diff --git a/src/Tgstation.Server.Host.Console.Debug/Tgstation.Server.Host.Console.Debug.csproj b/src/Tgstation.Server.Host.Console.Debug/Tgstation.Server.Host.Console.Debug.csproj deleted file mode 100644 index 6369c0169a..0000000000 --- a/src/Tgstation.Server.Host.Console.Debug/Tgstation.Server.Host.Console.Debug.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - Exe - netcoreapp2.0 - - - - 7.1 - - - - 7.1 - - - - - - - diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index 4bdac33a6d..e85a27b86b 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Logging; +using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Watchdog; @@ -35,8 +37,16 @@ namespace Tgstation.Server.Host.Console loggerFactory.CreateLogger(nameof(Program)).LogCritical("Please specify only 1 of --trace-host-watchdog or --debug-host-watchdog!"); return; } - //default CancellationToken because the Host handles that internally - await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), default).ConfigureAwait(false); + using (var cts = new CancellationTokenSource()) + { + AppDomain.CurrentDomain.ProcessExit += (a, b) => cts.Cancel(); + System.Console.CancelKeyPress += (a, b) => + { + b.Cancel = true; + cts.Cancel(); + }; + await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), cts.Token).ConfigureAwait(false); + } } } } diff --git a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj b/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj deleted file mode 100644 index 13866885f6..0000000000 --- a/src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netstandard2.0 - Full - 4.0.0.0 - 4.0.0.0 - - - - latest - true - - bin\Release\netstandard2.0\Tgstation.Server.Host.Startup..xml - - - - latest - - diff --git a/src/Tgstation.Server.Host.Watchdog/AbsoluteCompilationAssemblyResolver.cs b/src/Tgstation.Server.Host.Watchdog/AbsoluteCompilationAssemblyResolver.cs deleted file mode 100644 index a687111c28..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/AbsoluteCompilationAssemblyResolver.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.Extensions.DependencyModel; -using Microsoft.Extensions.DependencyModel.Resolution; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// for an absolute - /// - sealed class AbsoluteCompilationAssemblyResolver : ICompilationAssemblyResolver - { - /// - /// The base path - /// - readonly string basePath; - - /// - /// Construct a - /// - /// The value of - public AbsoluteCompilationAssemblyResolver(string basePath) - { - this.basePath = basePath ?? throw new ArgumentNullException(nameof(basePath)); - } - - /// - public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) - { - var paths = new List(); - - foreach (var assembly in library.Assemblies) - { - var assemblyFile = Path.Combine(basePath, assembly); - if (File.Exists(assemblyFile)) - paths.Add(assemblyFile); - else - return false; - } - - // only modify the assemblies parameter if we've resolved all files - assemblies?.AddRange(paths); - return true; - } - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IActiveLibraryDeleter.cs b/src/Tgstation.Server.Host.Watchdog/IActiveLibraryDeleter.cs deleted file mode 100644 index 34cbef1010..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/IActiveLibraryDeleter.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// For deleting libraries used by the program - /// - interface IActiveLibraryDeleter - { - /// - /// Deletes a library that is in use by the runtime - /// - /// The path of the library to delete - void DeleteActiveLibrary(string assemblyPath); - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IIsolatedAssemblyContextFactory.cs b/src/Tgstation.Server.Host.Watchdog/IIsolatedAssemblyContextFactory.cs deleted file mode 100644 index 88f90cf4ea..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/IIsolatedAssemblyContextFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Tgstation.Server.Host.Startup; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// For creating s in an isolated context from an - /// - interface IIsolatedAssemblyContextFactory - { - /// - /// Create a for an unloaded - /// - /// The path to the to create the from - /// A new - IServerFactory CreateIsolatedServerFactory(string assemblyPath); - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs deleted file mode 100644 index cfa1c05cc3..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedAssemblyContextFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.DotNet.PlatformAbstractions; -using Tgstation.Server.Host.Startup; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - sealed class IsolatedAssemblyContextFactory : IIsolatedAssemblyContextFactory - { - /// - public IServerFactory CreateIsolatedServerFactory(string assemblyPath) => new IsolatedServerFactory(assemblyPath, RuntimeEnvironment.GetRuntimeIdentifier()); - } -} diff --git a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs b/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs deleted file mode 100644 index 89c9e5ed38..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/IsolatedServerFactory.cs +++ /dev/null @@ -1,131 +0,0 @@ -using Microsoft.Extensions.DependencyModel; -using Microsoft.Extensions.DependencyModel.Resolution; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Loader; -using Tgstation.Server.Host.Startup; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// for loading s in a different - /// - sealed class IsolatedServerFactory : AssemblyLoadContext, IServerFactory - { - const string DllExtension = "dll"; - - static readonly string assemblyFileName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), DllExtension); - - /// - /// The path of the to load - /// - readonly string assemblyPath; - - /// - /// The runtime identifier to load dependencies from - /// - readonly string runtimeIdentifier; - - /// - /// The of the being loaded - /// - DependencyContext dependencyContext; - - /// - /// The for the - /// - ICompilationAssemblyResolver compilationAssemblyResolver; - - /// - /// Construct a - /// - /// The value of - /// The value of - public IsolatedServerFactory(string assemblyPath, string runtimeIdentifier) - { - this.assemblyPath = assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath)); - this.runtimeIdentifier = runtimeIdentifier ?? throw new ArgumentNullException(nameof(runtimeIdentifier)); - - Resolving += IsolatedServerFactory_Resolving; - } - - //https://stackoverflow.com/a/40921746/3976486 - //https://samcragg.wordpress.com/2017/06/30/resolving-assemblies-in-net-core/ - Assembly IsolatedServerFactory_Resolving(AssemblyLoadContext context, AssemblyName assemblyName) - { - if (assemblyName.Name.EndsWith("resources", StringComparison.Ordinal)) - return null; - - var library = dependencyContext.RuntimeLibraries.FirstOrDefault(x => String.Equals(x.Name, assemblyName.Name, StringComparison.OrdinalIgnoreCase)); - - if (library != null) - { - var wrapper = new CompilationLibrary( - library.Type, - library.Name, - library.Version, - library.Hash, - library.RuntimeAssemblyGroups.SelectMany(x => x.AssetPaths), - library.Dependencies, - library.Serviceable - ); - - var assemblies = new List(); - compilationAssemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies); - if (assemblies.Count > 0) - return context.LoadFromAssemblyPath(assemblies.First()); - } - - //could be not a "true" dependency i.e. CodeAnaylsis - var foundDll = Directory.GetFileSystemEntries(assemblyPath, String.Join(".", assemblyName.Name, DllExtension), SearchOption.AllDirectories).FirstOrDefault(); - if (foundDll != default) - return context.LoadFromAssemblyPath(foundDll); - return context.LoadFromAssemblyName(assemblyName); - } - - /// - /// Loads the at and creates an from it - /// - /// The arguments for the - /// The updatePath for the - /// A new - public IServer CreateServer(string[] args, string updatePath) - { - var assembly = LoadFromAssemblyPath(Path.Combine(assemblyPath, assemblyFileName)); - dependencyContext = DependencyContext.Load(assembly); - - var resolvers = new List(); - - var fallbacks = dependencyContext.RuntimeGraph.Where(x => x.Runtime == runtimeIdentifier).FirstOrDefault(); - - if(fallbacks != default) - { - var runtimesDirectory = Path.Combine(assemblyPath, "runtimes"); - resolvers.Add(new AbsoluteCompilationAssemblyResolver(Path.Combine(runtimesDirectory, runtimeIdentifier))); - resolvers.AddRange(fallbacks.Fallbacks.Select(x => new AbsoluteCompilationAssemblyResolver(Path.Combine(runtimesDirectory, x)))); - } - - resolvers.Add(new AppBaseCompilationAssemblyResolver(assemblyPath)); - - compilationAssemblyResolver = new CompositeCompilationAssemblyResolver(resolvers.ToArray()); - - - //find the IServerFactory implementation - var serverFactoryInterfaceType = typeof(IServerFactory); - - var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First(); - - var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType); - - return serverFactory.CreateServer(args, updatePath); - } - - //honestly have no idea what this is for, https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/assemblyloadcontext.md - /// - protected override Assembly Load(AssemblyName assemblyName) => null; - } -} diff --git a/src/Tgstation.Server.Host.Watchdog/NativeMethods.cs b/src/Tgstation.Server.Host.Watchdog/NativeMethods.cs deleted file mode 100644 index 1ce03329f7..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/NativeMethods.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Tgstation.Server.Host.Watchdog -{ - static class NativeMethods - { - public enum MoveFileFlags - { - None = 0, - ReplaceExisting = 1, - CopyAllowed = 2, - DelayUntilReboot = 4, - WriteThrough = 8, - CreateHardlink = 16, - FailIfNotTrackable = 32, - } - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags); - } -} \ No newline at end of file diff --git a/src/Tgstation.Server.Host.Watchdog/PosixActiveLibraryDeleter.cs b/src/Tgstation.Server.Host.Watchdog/PosixActiveLibraryDeleter.cs deleted file mode 100644 index c07b80572d..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/PosixActiveLibraryDeleter.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.IO; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// See for POSIX systems - /// - sealed class PosixActiveLibraryDeleter : IActiveLibraryDeleter - { - /// - public void DeleteActiveLibrary(string assemblyPath) => Directory.Delete(assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath)), true); //glory of inodes - } -} diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index e3764f0e2b..cd45758108 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -18,13 +18,7 @@ - - - - - - @@ -32,7 +26,7 @@ PreserveNewest - + diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index bd7cfe82ae..c2e2afe4c2 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -1,7 +1,12 @@ using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -10,15 +15,6 @@ namespace Tgstation.Server.Host.Watchdog /// sealed class Watchdog : IWatchdog { - /// - /// The for the - /// - readonly IActiveLibraryDeleter activeLibraryDeleter; - - /// - /// The for the - /// - readonly IIsolatedAssemblyContextFactory isolatedAssemblyLoader; /// /// The for the @@ -31,17 +27,33 @@ namespace Tgstation.Server.Host.Watchdog /// The value of /// The value of /// The value of - public Watchdog(IActiveLibraryDeleter activeLibraryDeleter, IIsolatedAssemblyContextFactory isolatedAssemblyLoader, ILogger logger) + public Watchdog(ILogger logger) { - this.activeLibraryDeleter = activeLibraryDeleter ?? throw new ArgumentNullException(nameof(activeLibraryDeleter)); - this.isolatedAssemblyLoader = isolatedAssemblyLoader ?? throw new ArgumentNullException(nameof(isolatedAssemblyLoader)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// public async Task RunAsync(string[] args, CancellationToken cancellationToken) { - const string DefaultAssemblyPath = "Default"; + logger.LogInformation("Host watchdog starting..."); + + var enviromentPath = Environment.GetEnvironmentVariable("PATH"); + var paths = enviromentPath.Split(';'); + + var exeName = "dotnet"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + exeName += ".exe"; + + var dotnetPath = paths.Select(x => Path.Combine(x, exeName)) + .Where(x => File.Exists(x)) + .FirstOrDefault(); + + if(dotnetPath == default) + { + 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); @@ -51,52 +63,158 @@ namespace Tgstation.Server.Host.Watchdog var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog - logger.LogInformation("Host watchdog starting..."); + var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); + var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); + var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName); - var nextAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, DefaultAssemblyPath)); + if (assemblyPath.Contains("\"")) + { + logger.LogCritical("Running from paths with \"'s in the name is not supported!"); + return; + } - string lastAssemblyPath = null; + if (!File.Exists(assemblyPath)) + { + logger.LogCritical("Unable to locate host assembly!"); + return; + } + + string updateDirectory = null; try { while (!cancellationToken.IsCancellationRequested) using (logger.BeginScope("Host invocation")) { - logger.LogTrace("Atttempting to create new server factory..."); - Guid updateGuid; - { //forces serverFactory out of the picture once the scope ends - var serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(nextAssemblyPath); - using (var server = serverFactory.CreateServer(args, assemblyStoragePath)) - { - logger.LogTrace("Running server..."); - await server.RunAsync(cancellationToken).ConfigureAwait(false); - logger.LogInformation("Active host exited."); + updateDirectory = Path.GetFullPath(Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString())); + using (var process = new Process()) + { + process.StartInfo.FileName = dotnetPath; + process.StartInfo.WorkingDirectory = Environment.CurrentDirectory; //for appsettings - if (!server.UpdateGuid.HasValue) + var arguments = new List + { + '"' + assemblyPath + '"', + updateDirectory + }; + arguments.AddRange(args); + + process.StartInfo.Arguments = String.Join(" ", arguments); + + process.StartInfo.UseShellExecute = false; //runs in the same console + + var tcs = new TaskCompletionSource(); + process.Exited += (a, b) => + { + tcs.TrySetResult(null); + }; + process.EnableRaisingEvents = true; + + logger.LogInformation("Launching host..."); + + var iShotTheSheriff = false; + try + { + process.Start(); + using (cancellationToken.Register(() => + { + if (!Directory.Exists(updateDirectory)) + { + logger.LogInformation("Cancellation requested! Writing shutdown lock file..."); + File.WriteAllBytes(updateDirectory, Array.Empty()); + } + else + logger.LogWarning("Cancellation requested while update directory exists!"); + + logger.LogInformation("Will force close host process if it doesn't exit in 10 seconds..."); + + Thread.Sleep(TimeSpan.FromSeconds(10)); //things get weird if we use tasks or other stuff + tcs.TrySetResult(null); + })) + await tcs.Task.ConfigureAwait(false); + } + catch (InvalidOperationException) { } + finally + { + try + { + if (!process.HasExited) + { + iShotTheSheriff = true; + process.Kill(); + process.WaitForExit(); + } + } + catch (InvalidOperationException) { } + logger.LogInformation("Host exited!"); + } + + switch (process.ExitCode) + { + case 0: + //just a restart + logger.LogInformation("Watchdog will restart host..."); break; - updateGuid = server.UpdateGuid.Value; + case 1: + //update path is now an exception document + logger.LogCritical("Host crashed, propagating exception dump..."); + var data = File.ReadAllText(updateDirectory); + try + { + File.Delete(updateDirectory); + } + catch (Exception e) + { + logger.LogWarning("Unable to delete exception dump file at {0}! Exception: {1}", updateDirectory, e); + } + throw new Exception(String.Format(CultureInfo.InvariantCulture, "Host propagated exception: {0}", data)); + default: + if (iShotTheSheriff) + { + logger.LogWarning("Watchdog forced to kill host process!"); + cancellationToken.ThrowIfCancellationRequested(); + } + throw new Exception(String.Format(CultureInfo.InvariantCulture, "Host crashed with exit code {0}!", process.ExitCode)); } } - logger.LogInformation("Update path is set to \"{0}\", attempting host assembly hotswap...", updateGuid); - GC.Collect(Int32.MaxValue, GCCollectionMode.Forced, true, true); - - activeLibraryDeleter.DeleteActiveLibrary(nextAssemblyPath); - - nextAssemblyPath = Path.Combine(assemblyStoragePath, updateGuid.ToString()); + if (Directory.Exists(updateDirectory)) + { + var tempPath = Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString()); + Directory.Move(defaultAssemblyPath, tempPath); + try + { + Directory.Move(updateDirectory, defaultAssemblyPath); + logger.LogInformation("Server update complete, deleting old server..."); + try + { + Directory.Delete(tempPath, true); + } + catch (Exception e) + { + logger.LogWarning("Error deleting old server at {0}! Exception: {1}", tempPath, e); + } + } + catch (Exception e) + { + logger.LogError("Error moving updated server directory, attempting revert! Exception: {0}", e); + Directory.Delete(defaultAssemblyPath, true); + Directory.Move(tempPath, defaultAssemblyPath); + logger.LogInformation("Revert successful!"); + } + } } } catch (OperationCanceledException) { logger.LogDebug("Exiting due to cancellation..."); + if (!Directory.Exists(updateDirectory)) + File.Delete(updateDirectory); + else + Directory.Delete(updateDirectory, true); } catch (Exception e) { - logger.LogCritical("Error running host assembly! Exception: {0}", e); - nextAssemblyPath = lastAssemblyPath ?? DefaultAssemblyPath; //don't wanna save a critfailed assembly - } - if (nextAssemblyPath != DefaultAssemblyPath) - { - logger.LogInformation("Setting next default host assembly path to {0}...", nextAssemblyPath); + logger.LogCritical("Watchdog error! Exception: {0}", e); } logger.LogInformation("Host watchdog exiting..."); } diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs index f9c11a511c..05934ba491 100644 --- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs @@ -10,6 +10,6 @@ namespace Tgstation.Server.Host.Watchdog { /// [ExcludeFromCodeCoverage] - public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveLibraryDeleter)new WindowsActiveLibraryDeleter() : new PosixActiveLibraryDeleter(), new IsolatedAssemblyContextFactory(), loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory))); + public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory))); } } diff --git a/src/Tgstation.Server.Host.Watchdog/WindowsActiveLibraryDeleter.cs b/src/Tgstation.Server.Host.Watchdog/WindowsActiveLibraryDeleter.cs deleted file mode 100644 index a2a98b4bac..0000000000 --- a/src/Tgstation.Server.Host.Watchdog/WindowsActiveLibraryDeleter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Runtime.InteropServices; - -namespace Tgstation.Server.Host.Watchdog -{ - /// - /// See for Windows systems - /// - sealed class WindowsActiveLibraryDeleter : IActiveLibraryDeleter - { - /// - /// Set a directory located at to be deleted on reboot - /// - /// The file to delete on reboot - [ExcludeFromCodeCoverage] - static void DeleteDirectoryOnReboot(string path) - { - if (!NativeMethods.MoveFileEx(path, null, NativeMethods.MoveFileFlags.DelayUntilReboot)) - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - /// - public void DeleteActiveLibrary(string assemblyPath) - { - if (assemblyPath == null) - throw new ArgumentNullException(nameof(assemblyPath)); - - var tmpLocation = Path.Combine(Path.GetDirectoryName(assemblyPath), Guid.NewGuid().ToString()); - Directory.Move(assemblyPath, tmpLocation); - DeleteDirectoryOnReboot(tmpLocation); - } - } -} diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 2bb8dcff06..1738af477b 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -49,7 +49,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// readonly List mappedChannels; - static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", ""); + static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", "", StringComparison.Ordinal); /// /// Construct a diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 13dde17437..21e3cbcd56 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The access string to fetch from the origin repository /// The for the operation /// Optional function to report 0-100 progress of the clone - /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict + /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action progressReporter, CancellationToken cancellationToken); /// @@ -87,7 +87,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The name of the merge committer /// The e-mail of the merge committer /// The for the operation - /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict + /// A resulting in a representing the merge result that is after a fast forward or up to date, on a merge, on a conflict Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 9dbab666f3..b0f3a769dd 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -122,7 +122,7 @@ namespace Tgstation.Server.Host.Components.Repository if (!url.ToUpperInvariant().StartsWith(HttProtocolSecure, StringComparison.InvariantCulture)) throw new InvalidOperationException("Cannot use access string without HTTPS remote!"); //ONLY support https urls - return url.ToUpperInvariant().Replace(HttProtocolSecure, String.Concat(HttProtocolSecure, accessString, '@')); + return url.ToUpperInvariant().Replace(HttProtocolSecure, String.Concat(HttProtocolSecure, accessString, '@'), StringComparison.Ordinal); } /// diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index e8d8ca75dc..f690dc720d 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Controllers Version greatestVersion = null; foreach (var I in releases) - if (Version.TryParse(I.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty), out var version) + if (Version.TryParse(I.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) && version.Major == application.Version.Major && (greatestVersion == null || version > greatestVersion)) greatestVersion = version; @@ -143,7 +143,7 @@ namespace Tgstation.Server.Host.Controllers } foreach (var release in releases) - if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty), out var version) && version == model.CurrentVersion) + if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) && version == model.CurrentVersion) { var asset = release.Assets.Where(x => x.Name == updatesConfiguration.UpdatePackageAssetName).FirstOrDefault(); if (asset == default) @@ -152,7 +152,8 @@ namespace Tgstation.Server.Host.Controllers var assetBytes = await ioManager.DownloadFile(new Uri(asset.BrowserDownloadUrl), cancellationToken).ConfigureAwait(false); try { - await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false); + if (!await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false)) + return StatusCode((int)HttpStatusCode.NotImplemented); } catch (InvalidOperationException) { } //we were beat to the punch return Ok(); //gtfo of here before all the cancellation tokens fire diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index f05938f75a..56de5ba872 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -129,7 +129,7 @@ namespace Tgstation.Server.Host.Controllers var uiBad = BadGitHubUrl.ToUpperInvariant(); var uiGitHub = Components.Repository.Repository.GitHubUrl.ToUpperInvariant(); if (uiOrigin.Contains(uiBad)) - model.Origin = uiOrigin.Replace(uiBad, uiGitHub); + model.Origin = uiOrigin.Replace(uiBad, uiGitHub, StringComparison.Ordinal); currentModel.AccessToken = model.AccessToken; currentModel.AccessUser = model.AccessUser; //intentionally only these fields, user not allowed to change anything else atm diff --git a/src/Tgstation.Server.Host/Core/IServerUpdater.cs b/src/Tgstation.Server.Host/Core/IServerUpdater.cs index 270ffc1636..91fa46b2e7 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdater.cs +++ b/src/Tgstation.Server.Host/Core/IServerUpdater.cs @@ -11,13 +11,13 @@ namespace Tgstation.Server.Host.Core public interface IServerUpdater { /// - /// Run a new assembly and stop the current one. This will likely trigger all active s + /// Run a new assembly and stop the current one. This will likely trigger all active s /// /// The s of the .zip file that contains the new assembly /// The for the operation /// The for the operation - /// A representing the running operation - Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken); + /// A resulting in if live updates are supported, otherwise + Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken); /// /// Register a given to run before stopping the server for updates diff --git a/src/Tgstation.Server.Host.Startup/IServer.cs b/src/Tgstation.Server.Host/IServer.cs similarity index 66% rename from src/Tgstation.Server.Host.Startup/IServer.cs rename to src/Tgstation.Server.Host/IServer.cs index 3ab78291f5..e15152cbd4 100644 --- a/src/Tgstation.Server.Host.Startup/IServer.cs +++ b/src/Tgstation.Server.Host/IServer.cs @@ -2,18 +2,13 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Startup +namespace Tgstation.Server.Host { /// /// Represents the host /// public interface IServer : IDisposable { - /// - /// The path to the updated assembly to run if any. Populated once returns - /// - Guid? UpdateGuid { get; } - /// /// Runs the /// diff --git a/src/Tgstation.Server.Host.Startup/IServerFactory.cs b/src/Tgstation.Server.Host/IServerFactory.cs similarity index 92% rename from src/Tgstation.Server.Host.Startup/IServerFactory.cs rename to src/Tgstation.Server.Host/IServerFactory.cs index a787e083f8..f907c3be5b 100644 --- a/src/Tgstation.Server.Host.Startup/IServerFactory.cs +++ b/src/Tgstation.Server.Host/IServerFactory.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Startup +namespace Tgstation.Server.Host { /// /// For creating s diff --git a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs index 828012b05b..011893939c 100644 --- a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs @@ -22,6 +22,7 @@ namespace Tgstation.Server.Host.Models /// The for the /// The of for the /// The for the + /// The value of public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); diff --git a/src/Tgstation.Server.Host/Program.cs b/src/Tgstation.Server.Host/Program.cs new file mode 100644 index 0000000000..f8722c045e --- /dev/null +++ b/src/Tgstation.Server.Host/Program.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host +{ + /// + /// Entrypoint for the + /// + static class Program + { + /// + /// The to use + /// + internal static IServerFactory serverFactory = new ServerFactory(); + + /// + /// Entrypoint for the + /// + /// The command line arguments + /// The + public static async Task Main(string[] args) + { + var listArgs = new List(args); + //first arg is 100% always the update path + string updatePath; + if (listArgs.Count > 0) + { + updatePath = listArgs[0]; + listArgs.RemoveAt(0); +#if DEBUG + System.Diagnostics.Debugger.Launch(); +#endif + } + else + updatePath = null; + try + { + using (var cts = new CancellationTokenSource()) + { + AppDomain.CurrentDomain.ProcessExit += (a, b) => cts.Cancel(); + Console.CancelKeyPress += (a, b) => + { + b.Cancel = true; + cts.Cancel(); + }; + using (var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath)) + await server.RunAsync(cts.Token).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + catch (Exception e) + { + if (updatePath != null) + { + File.WriteAllText(updatePath, e.ToString()); + return 1; + } + throw; + } + return 0; + } + } +} diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index c49fbbad8b..1dee6ddbe9 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -1,21 +1,18 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using System; +using System.IO; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host { /// sealed class Server : IServer, IServerUpdater { - /// - public Guid? UpdateGuid { get; private set; } - /// /// The for the /// @@ -31,6 +28,11 @@ namespace Tgstation.Server.Host /// readonly string updatePath; + /// + /// If a server update has been applied + /// + bool updated; + /// /// The for the /// @@ -44,9 +46,10 @@ namespace Tgstation.Server.Host public Server(IWebHostBuilder webHostBuilder, string updatePath) { this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder)); - this.updatePath = updatePath ?? throw new ArgumentNullException(nameof(updatePath)); + this.updatePath = updatePath; semaphore = new SemaphoreSlim(1); + updated = false; } /// @@ -57,32 +60,53 @@ namespace Tgstation.Server.Host public async Task RunAsync(CancellationToken cancellationToken) { using (cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) - using (var webHost = webHostBuilder - .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) - .Build() - ) - await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false); + using (var fsWatcher = updatePath != null ? new FileSystemWatcher(Path.GetDirectoryName(updatePath)) : null) + { + if (fsWatcher != null) + { + fsWatcher.Created += (a, b) => + { + if (b.Name == updatePath && File.Exists(b.FullPath)) + cancellationTokenSource.Cancel(); + }; + fsWatcher.EnableRaisingEvents = true; + } + using (var webHost = webHostBuilder + .UseStartup() + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .Build() + ) + await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false); + } } /// - public async Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) + public async Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) { - using(await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) + if (updatePath == null) + return false; + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { - if (UpdateGuid != null) + if (updated) throw new InvalidOperationException("ApplyUpdate has already been called!"); - UpdateGuid = Guid.NewGuid(); + updated = true; try { await ioManager.ZipToDirectory(updatePath, updateZipData, cancellationToken).ConfigureAwait(false); } catch { - UpdateGuid = null; + try + { + //important to not leave this directory around if possible + await ioManager.DeleteDirectory(updatePath, default).ConfigureAwait(false); + } + catch { } + updated = false; throw; } Restart(); + return true; } } diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index ffe440055d..61f2984323 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -1,10 +1,9 @@ using Microsoft.AspNetCore; -using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host { /// - public sealed class ServerFactory : IServerFactory + sealed class ServerFactory : IServerFactory { /// public IServer CreateServer(string[] args, string updatePath) => new Server(WebHost.CreateDefaultBuilder(args), updatePath); diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 4a5ae909e1..a853acc041 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -1,7 +1,8 @@ - netstandard2.0 + Exe + netcoreapp2.0 Full 4.0.0.0 @@ -19,11 +20,6 @@ 1701;1702;1705;CA2227 - - - - - @@ -39,7 +35,7 @@ - + @@ -55,7 +51,12 @@ - + + + + + PreserveNewest + diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index da80b4917b..4ee3d58082 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Core.Tests [TestClass] public sealed class TestApplication : IServerUpdater { - public Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException(); public void RegisterForUpdate(Action action) => throw new NotImplementedException(); diff --git a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs index 7f3c05c8a2..67c3eb9289 100644 --- a/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs +++ b/tests/Tgstation.Server.Host.Watchdog.Tests/TestWatchdog.cs @@ -4,7 +4,6 @@ using Moq; using System; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Startup; namespace Tgstation.Server.Host.Watchdog.Tests { @@ -14,13 +13,9 @@ namespace Tgstation.Server.Host.Watchdog.Tests [TestMethod] public void TestConstruction() { - Assert.ThrowsException(() => new Watchdog(null, null, null)); - var mockActiveAssemblyDeleter = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockActiveAssemblyDeleter.Object, null, null)); - var mockIsolatedServerContextFactory = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, null)); + Assert.ThrowsException(() => new Watchdog(null)); var mockLogger = new LoggerFactory().CreateLogger(); - var wd = new Watchdog(mockActiveAssemblyDeleter.Object, mockIsolatedServerContextFactory.Object, mockLogger); + var wd = new Watchdog(mockLogger); } } } diff --git a/tgstation-server.sln b/tgstation-server.sln index 1ae18e7168..acd602d242 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -59,8 +59,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watch EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Watchdog.Tests", "tests\Tgstation.Server.Host.Watchdog.Tests\Tgstation.Server.Host.Watchdog.Tests.csproj", "{7500F776-4384-4B5F-A8D8-22461CAD108B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Startup", "src\Tgstation.Server.Host.Startup\Tgstation.Server.Host.Startup.csproj", "{7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Service.Tests", "tests\Tgstation.Server.Host.Service.Tests\Tgstation.Server.Host.Service.Tests.csproj", "{AA80A190-52E2-4BE3-BFEB-1F148D9E9007}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DMAPI", "DMAPI", "{7E4E7CF6-A48D-410E-8191-C49C6B5AB9CC}" @@ -108,8 +106,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{A55C1117 tools\TGS.postman_collection.json = tools\TGS.postman_collection.json EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Console.Debug", "src\Tgstation.Server.Host.Console.Debug\Tgstation.Server.Host.Console.Debug.csproj", "{D32A005E-D920-4E79-9EB9-8671ABC0E2E3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -207,24 +203,12 @@ Global {7500F776-4384-4B5F-A8D8-22461CAD108B}.Docker|Any CPU.Build.0 = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7500F776-4384-4B5F-A8D8-22461CAD108B}.Release|Any CPU.Build.0 = Release|Any CPU - {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Docker|Any CPU.ActiveCfg = Release|Any CPU - {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Docker|Any CPU.Build.0 = Release|Any CPU - {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7D067E1B-06A1-49C8-A8E6-7ACBA28A5A41}.Release|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.ActiveCfg = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Docker|Any CPU.Build.0 = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA80A190-52E2-4BE3-BFEB-1F148D9E9007}.Release|Any CPU.Build.0 = Release|Any CPU - {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Docker|Any CPU.ActiveCfg = Debug|Any CPU - {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Docker|Any CPU.Build.0 = Debug|Any CPU - {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D32A005E-D920-4E79-9EB9-8671ABC0E2E3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 189bbafe4c9eebe6823b80e003ab54b66131e2cc Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 15:45:59 -0400 Subject: [PATCH 387/397] Auto updates working at last --- src/Tgstation.Server.Host.Console/Program.cs | 20 ++-- .../Tgstation.Server.Host.Watchdog.csproj | 9 -- .../Watchdog.cs | 92 ++++++++++++++----- .../Components/InstanceFactory.cs | 6 +- .../Components/Watchdog/Watchdog.cs | 6 +- .../Components/Watchdog/WatchdogFactory.cs | 6 +- .../Controllers/AdministrationController.cs | 13 +-- .../Controllers/DreamDaemonController.cs | 3 +- src/Tgstation.Server.Host/Core/Application.cs | 7 +- .../{IServerUpdater.cs => IServerControl.cs} | 9 +- src/Tgstation.Server.Host/IServer.cs | 5 + src/Tgstation.Server.Host/Program.cs | 47 ++++++---- src/Tgstation.Server.Host/Server.cs | 27 ++++-- .../Tgstation.Server.Host.csproj | 4 + .../appsettings.Docker.json | 0 .../appsettings.json | 0 .../Core/TestApplication.cs | 8 +- 17 files changed, 174 insertions(+), 88 deletions(-) rename src/Tgstation.Server.Host/Core/{IServerUpdater.cs => IServerControl.cs} (84%) rename src/{Tgstation.Server.Host.Watchdog => Tgstation.Server.Host}/appsettings.Docker.json (100%) rename src/{Tgstation.Server.Host.Watchdog => Tgstation.Server.Host}/appsettings.json (100%) diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs index e85a27b86b..0a62b2ed4e 100644 --- a/src/Tgstation.Server.Host.Console/Program.cs +++ b/src/Tgstation.Server.Host.Console/Program.cs @@ -39,13 +39,21 @@ namespace Tgstation.Server.Host.Console } using (var cts = new CancellationTokenSource()) { - AppDomain.CurrentDomain.ProcessExit += (a, b) => cts.Cancel(); - System.Console.CancelKeyPress += (a, b) => + void AppDomainHandler(object a, EventArgs b) => cts.Cancel(); + AppDomain.CurrentDomain.ProcessExit += AppDomainHandler; + try { - b.Cancel = true; - cts.Cancel(); - }; - await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), cts.Token).ConfigureAwait(false); + System.Console.CancelKeyPress += (a, b) => + { + b.Cancel = true; + cts.Cancel(); + }; + await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), cts.Token).ConfigureAwait(false); + } + finally + { + AppDomain.CurrentDomain.ProcessExit -= AppDomainHandler; + } } } } diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index cd45758108..e45d409e53 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -26,13 +26,4 @@ PreserveNewest - - - - - - - - - diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index c2e2afe4c2..f48b999c99 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -39,9 +39,10 @@ namespace Tgstation.Server.Host.Watchdog var enviromentPath = Environment.GetEnvironmentVariable("PATH"); var paths = enviromentPath.Split(';'); + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var exeName = "dotnet"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (isWindows) exeName += ".exe"; var dotnetPath = paths.Select(x => Path.Combine(x, exeName)) @@ -57,13 +58,26 @@ namespace Tgstation.Server.Host.Watchdog var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog + var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); #if DEBUG - rootLocation = Path.GetFullPath("../../../../Tgstation.Server.Host.Watchdog/bin/Debug/netstandard2.0"); + //just copy the shit where it belongs + Directory.Delete(assemblyStoragePath, true); + Directory.CreateDirectory(defaultAssemblyPath); + + var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.0"; + foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) + Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath)); + + foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) + File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true); + + const string AppSettingsJson = "appsettings.json"; + var rootJson = Path.Combine(rootLocation, AppSettingsJson); + File.Delete(rootJson); + File.Move(Path.Combine(defaultAssemblyPath, AppSettingsJson), rootJson); #endif - var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog - - var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName); @@ -86,6 +100,7 @@ namespace Tgstation.Server.Host.Watchdog using (logger.BeginScope("Host invocation")) { updateDirectory = Path.GetFullPath(Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString())); + logger.LogInformation("Update path set to {0}", updateDirectory); using (var process = new Process()) { process.StartInfo.FileName = dotnetPath; @@ -96,6 +111,8 @@ namespace Tgstation.Server.Host.Watchdog '"' + assemblyPath + '"', updateDirectory }; + if (Debugger.IsAttached) + arguments.Add("--attach-debugger"); arguments.AddRange(args); process.StartInfo.Arguments = String.Join(" ", arguments); @@ -108,13 +125,16 @@ namespace Tgstation.Server.Host.Watchdog tcs.TrySetResult(null); }; process.EnableRaisingEvents = true; - + logger.LogInformation("Launching host..."); var iShotTheSheriff = false; try { process.Start(); + + using (var processCts = new CancellationTokenSource()) + using (processCts.Token.Register(() => tcs.TrySetResult(null))) using (cancellationToken.Register(() => { if (!Directory.Exists(updateDirectory)) @@ -127,8 +147,11 @@ namespace Tgstation.Server.Host.Watchdog logger.LogInformation("Will force close host process if it doesn't exit in 10 seconds..."); - Thread.Sleep(TimeSpan.FromSeconds(10)); //things get weird if we use tasks or other stuff - tcs.TrySetResult(null); + try + { + processCts.CancelAfter(TimeSpan.FromSeconds(10)); + } + catch (ObjectDisposedException) { } //race conditions })) await tcs.Task.ConfigureAwait(false); } @@ -151,10 +174,15 @@ namespace Tgstation.Server.Host.Watchdog switch (process.ExitCode) { case 0: - //just a restart - logger.LogInformation("Watchdog will restart host..."); - break; + return; case 1: + if (!cancellationToken.IsCancellationRequested) + //just a restart + logger.LogInformation("Watchdog will restart host..."); + else + logger.LogWarning("Host requested restart but watchdog shutdown is in progress!"); + break; + case 2: //update path is now an exception document logger.LogCritical("Host crashed, propagating exception dump..."); var data = File.ReadAllText(updateDirectory); @@ -177,29 +205,46 @@ namespace Tgstation.Server.Host.Watchdog } } + //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 + GC.Collect(); + await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false); + } var tempPath = Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString()); - Directory.Move(defaultAssemblyPath, tempPath); try { - Directory.Move(updateDirectory, defaultAssemblyPath); - logger.LogInformation("Server update complete, deleting old server..."); + Directory.Move(defaultAssemblyPath, tempPath); try { - Directory.Delete(tempPath, true); + Directory.Move(updateDirectory, defaultAssemblyPath); + logger.LogInformation("Server update complete, deleting old server..."); + try + { + Directory.Delete(tempPath, true); + } + catch (Exception e) + { + logger.LogWarning("Error deleting old server at {0}! Exception: {1}", tempPath, e); + } } catch (Exception e) { - logger.LogWarning("Error deleting old server at {0}! Exception: {1}", tempPath, e); + logger.LogError("Error moving updated server directory, attempting revert! Exception: {0}", e); + Directory.Delete(defaultAssemblyPath, true); + Directory.Move(tempPath, defaultAssemblyPath); + logger.LogInformation("Revert successful!"); } } - catch (Exception e) + catch(Exception e) { - logger.LogError("Error moving updated server directory, attempting revert! Exception: {0}", e); - Directory.Delete(defaultAssemblyPath, true); - Directory.Move(tempPath, defaultAssemblyPath); - logger.LogInformation("Revert successful!"); + logger.LogWarning("Failed to move out active host assembly! Exception: {0}", e); } } } @@ -216,7 +261,10 @@ namespace Tgstation.Server.Host.Watchdog { logger.LogCritical("Watchdog error! Exception: {0}", e); } - logger.LogInformation("Host watchdog exiting..."); + finally + { + logger.LogInformation("Host watchdog exiting..."); + } } } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 9646712c84..2994bf1ef5 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -43,9 +43,9 @@ namespace Tgstation.Server.Host.Components readonly IByondTopicSender byondTopicSender; /// - /// The for the + /// The for the /// - readonly IServerUpdater serverUpdater; + readonly IServerControl serverUpdater; /// /// The for the @@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor) + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index c27db4fed0..6c0d58c864 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -131,7 +131,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - /// The for the + /// The for the /// The value of /// The value of /// The value of @@ -140,7 +140,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); @@ -156,7 +156,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (serverUpdater == null) throw new ArgumentNullException(nameof(serverUpdater)); - serverUpdater.RegisterForUpdate(() => releaseServers = true); + serverUpdater.RegisterForRestart(() => releaseServers = true); chat.RegisterCommandHandler(this); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index 2cf4f407af..65b0452042 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -22,9 +22,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly ISessionControllerFactory sessionControllerFactory; /// - /// The for the + /// The for the /// - readonly IServerUpdater serverUpdater; + readonly IServerControl serverUpdater; /// /// The for the @@ -69,7 +69,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance) + public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerControl serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index f690dc720d..551dca9dc6 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -36,9 +36,9 @@ namespace Tgstation.Server.Host.Controllers readonly IGitHubClient gitHubClient; /// - /// The for the + /// The for the /// - readonly IServerUpdater serverUpdater; + readonly IServerControl serverUpdater; /// /// The for the @@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The containing value of - public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false) + public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); @@ -163,11 +163,8 @@ namespace Tgstation.Server.Host.Controllers } /// + [HttpDelete] [TgsAuthorize(AdministrationRights.RestartHost)] - public override Task Delete(long id, CancellationToken cancellationToken) - { - serverUpdater.Restart(); - return Task.FromResult((IActionResult)Ok()); - } + public Task Delete() => Task.FromResult(serverUpdater.Restart() ? (IActionResult)Ok() : StatusCode((int)HttpStatusCode.NotImplemented)); } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 4d805cca10..34d5be50c0 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -114,8 +114,9 @@ namespace Tgstation.Server.Host.Controllers } /// + [HttpDelete] [TgsAuthorize(DreamDaemonRights.Shutdown)] - public override async Task Delete(long id, CancellationToken cancellationToken) + public async Task Delete(CancellationToken cancellationToken) { //alias for stopping DD var instance = instanceManager.GetInstance(Instance); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index b1108ed177..9674267ce9 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -232,10 +232,15 @@ namespace Tgstation.Server.Host.Core /// Configure the /// /// The to configure - public void Configure(IApplicationBuilder applicationBuilder) + /// The for the + public void Configure(IApplicationBuilder applicationBuilder, ILogger logger) { if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + + logger.LogInformation(VersionString); serverAddresses = applicationBuilder.ServerFeatures.Get(); diff --git a/src/Tgstation.Server.Host/Core/IServerUpdater.cs b/src/Tgstation.Server.Host/Core/IServerControl.cs similarity index 84% rename from src/Tgstation.Server.Host/Core/IServerUpdater.cs rename to src/Tgstation.Server.Host/Core/IServerControl.cs index 91fa46b2e7..377b7d4a9c 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdater.cs +++ b/src/Tgstation.Server.Host/Core/IServerControl.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Core /// /// Represents a service that may take an updated assembly and run it, stopping the current assembly in the process /// - public interface IServerUpdater + public interface IServerControl { /// /// Run a new assembly and stop the current one. This will likely trigger all active s @@ -20,14 +20,15 @@ namespace Tgstation.Server.Host.Core Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken); /// - /// Register a given to run before stopping the server for updates + /// Register a given to run before stopping the server for a restart /// /// The to run - void RegisterForUpdate(Action action); + void RegisterForRestart(Action action); /// /// Restarts the /// - void Restart(); + /// if live restarts are supported, otherwise + bool Restart(); } } diff --git a/src/Tgstation.Server.Host/IServer.cs b/src/Tgstation.Server.Host/IServer.cs index e15152cbd4..531b3b8afc 100644 --- a/src/Tgstation.Server.Host/IServer.cs +++ b/src/Tgstation.Server.Host/IServer.cs @@ -9,6 +9,11 @@ namespace Tgstation.Server.Host /// public interface IServer : IDisposable { + /// + /// If the should restart + /// + bool RestartRequested { get; } + /// /// Runs the /// diff --git a/src/Tgstation.Server.Host/Program.cs b/src/Tgstation.Server.Host/Program.cs index f8722c045e..65f5199f40 100644 --- a/src/Tgstation.Server.Host/Program.cs +++ b/src/Tgstation.Server.Host/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -7,7 +8,7 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host { /// - /// Entrypoint for the + /// Entrypoint for the /// static class Program { @@ -20,47 +21,59 @@ namespace Tgstation.Server.Host /// Entrypoint for the /// /// The command line arguments - /// The + /// The public static async Task Main(string[] args) { var listArgs = new List(args); - //first arg is 100% always the update path + //first arg is 100% always the update path, starting it otherwise is solely for debugging purposes string updatePath; if (listArgs.Count > 0) { updatePath = listArgs[0]; listArgs.RemoveAt(0); -#if DEBUG - System.Diagnostics.Debugger.Launch(); -#endif + if (listArgs.Remove("--attach-debugger")) + Debugger.Launch(); } else updatePath = null; try { - using (var cts = new CancellationTokenSource()) + using (var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath)) { - AppDomain.CurrentDomain.ProcessExit += (a, b) => cts.Cancel(); - Console.CancelKeyPress += (a, b) => + try { - b.Cancel = true; - cts.Cancel(); - }; - using (var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath)) - await server.RunAsync(cts.Token).ConfigureAwait(false); + using (var cts = new CancellationTokenSource()) + { + void AppDomainHandler(object a, EventArgs b) => cts.Cancel(); + AppDomain.CurrentDomain.ProcessExit += AppDomainHandler; + try + { + Console.CancelKeyPress += (a, b) => + { + b.Cancel = true; + cts.Cancel(); + }; + await server.RunAsync(cts.Token).ConfigureAwait(false); + } + finally + { + AppDomain.CurrentDomain.ProcessExit -= AppDomainHandler; + } + } + } + catch (OperationCanceledException) { } + return server.RestartRequested ? 1 : 0; } } - catch (OperationCanceledException) { } catch (Exception e) { if (updatePath != null) { File.WriteAllText(updatePath, e.ToString()); - return 1; + return 2; } throw; } - return 0; } } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 1dee6ddbe9..f24dc3b4e9 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -11,8 +11,11 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host { /// - sealed class Server : IServer, IServerUpdater + sealed class Server : IServer, IServerControl { + /// + public bool RestartRequested { get; private set; } + /// /// The for the /// @@ -32,7 +35,7 @@ namespace Tgstation.Server.Host /// If a server update has been applied /// bool updated; - + /// /// The for the /// @@ -50,6 +53,7 @@ namespace Tgstation.Server.Host semaphore = new SemaphoreSlim(1); updated = false; + RestartRequested = false; } /// @@ -66,14 +70,14 @@ namespace Tgstation.Server.Host { fsWatcher.Created += (a, b) => { - if (b.Name == updatePath && File.Exists(b.FullPath)) + if (b.FullPath == updatePath && File.Exists(b.FullPath)) cancellationTokenSource.Cancel(); }; fsWatcher.EnableRaisingEvents = true; } using (var webHost = webHostBuilder .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false); @@ -111,19 +115,28 @@ namespace Tgstation.Server.Host } /// - public void RegisterForUpdate(Action action) + public void RegisterForRestart(Action action) { + if (action == null) + throw new ArgumentNullException(nameof(action)); if (cancellationTokenSource == null) throw new InvalidOperationException("Tried to register an update action on a non-running Server!"); - cancellationTokenSource.Token.Register(action); + cancellationTokenSource.Token.Register(() => { + if (RestartRequested) + action(); + }); } /// - public void Restart() + public bool Restart() { + if (updatePath == null) + return false; if (cancellationTokenSource == null) throw new InvalidOperationException("Tried to restart a non-running Server!"); + RestartRequested = true; cancellationTokenSource.Cancel(); + return true; } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index a853acc041..85d750b8fc 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -59,4 +59,8 @@ + + + + diff --git a/src/Tgstation.Server.Host.Watchdog/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json similarity index 100% rename from src/Tgstation.Server.Host.Watchdog/appsettings.Docker.json rename to src/Tgstation.Server.Host/appsettings.Docker.json diff --git a/src/Tgstation.Server.Host.Watchdog/appsettings.json b/src/Tgstation.Server.Host/appsettings.json similarity index 100% rename from src/Tgstation.Server.Host.Watchdog/appsettings.json rename to src/Tgstation.Server.Host/appsettings.json diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index 4ee3d58082..319b6d94c8 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -11,13 +11,13 @@ using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Core.Tests { [TestClass] - public sealed class TestApplication : IServerUpdater + public sealed class TestApplication : IServerControl { public Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException(); - public void RegisterForUpdate(Action action) => throw new NotImplementedException(); + public void RegisterForRestart(Action action) => throw new NotImplementedException(); - public void Restart() => throw new NotImplementedException(); + public bool Restart() => throw new NotImplementedException(); [TestMethod] public async Task TestSuccessfulStartup() @@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Core.Tests { using (var webHost = WebHost.CreateDefaultBuilder(new string[] { "Database:DatabaseType=Sqlite", "Database:ConnectionString=Data Source=" + dbName }) //force it to use sqlite .UseStartup() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) + .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) .Build() ) { From c758c39a970adf43a47caa836def06e18daeefa3 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 15:50:56 -0400 Subject: [PATCH 388/397] Fix appveyor --- appveyor.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index f331baffb1..92577863f2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -58,13 +58,17 @@ after_test: - ps: build/UploadCoverage.ps1 - ps: build/BuildDox.ps1 #host updater - - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION% --no-build + - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION% #console - - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% --no-build + - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION% + - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerConsole/lib/Default -recurse + - ps: Move-Item -path artifacts/ServerConsole/lib/Default/appsettings.json -destination artifacts/ServerConsole/ #service - ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination artifacts/ServerService -recurse - ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerService/lib/Default -recurse + - ps: Move-Item -path artifacts/ServerService/lib/Default/appsettings.json -destination artifacts/ServerService/ #deploy stuff + - ps: Remove-Item artifacts/ServerHost/appsettings.json - 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." }}} From 507fb2dfa7d55c06c106a91cb06737b1e7481a0d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 16:26:36 -0400 Subject: [PATCH 389/397] Hopefully fixes things --- .../Tgstation.Server.Host.Watchdog.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index e45d409e53..f6c9dce932 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -3,6 +3,7 @@ netstandard2.0 Full + false From c52c2b3cc27dda8e6ca5be0cbebbea85f6b1d1e0 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 16:31:04 -0400 Subject: [PATCH 390/397] Fixes Dockerfile --- build/Dockerfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 9d8837adc2..af174c8317 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -5,7 +5,6 @@ COPY tgstation-server.sln ./ COPY src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj src/Tgstation.Server.Host.Console/ COPY src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj src/Tgstation.Server.Host.Watchdog/ -COPY src/Tgstation.Server.Host.Startup/Tgstation.Server.Host.Startup.csproj src/Tgstation.Server.Host.Startup/ COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/ COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/ @@ -18,9 +17,6 @@ RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=fa COPY . . -WORKDIR /src/src/Tgstation.Server.Host.Startup -RUN dotnet build -c Release - WORKDIR /src/tests/Tgstation.Server.Api.Tests RUN dotnet test -c Release @@ -46,6 +42,7 @@ WORKDIR /app COPY --from=build /app . COPY --from=build /src/build/tgs.docker.sh tgs.sh +COPY --from=build /src/src/Tgstation.Server.Host/appsettings.Docker.json . RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json VOLUME ["/config_data", "/tgs_logs"] From 4d64fcd18a347c2856032d5df388576c2a8fe025 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 16:34:26 -0400 Subject: [PATCH 391/397] Fix release build --- src/Tgstation.Server.Host.Watchdog/Watchdog.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index f48b999c99..44bccaeb37 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -24,8 +24,6 @@ namespace Tgstation.Server.Host.Watchdog /// /// Construct a /// - /// The value of - /// The value of /// The value of public Watchdog(ILogger logger) { From 16575b9b970445f2c90d6cb4aa96bdc97378640e Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 16:39:18 -0400 Subject: [PATCH 392/397] Fix api versioning --- src/Tgstation.Server.Api/ApiHeaders.cs | 19 +++++++++++-------- .../Tgstation.Server.Api.csproj | 1 + 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index 5db3493ae5..597bad25cc 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -119,21 +119,23 @@ namespace Tgstation.Server.Api if (!requestHeaders.Accept.Any(x => x.MediaType == jsonAccept.MediaType)) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", ApplicationJson)); - if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out StringValues userAgentValues) || !ProductInfoHeaderValue.TryParse(userAgentValues.FirstOrDefault(), out ProductInfoHeaderValue clientUserAgent)) + 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 - if (String.IsNullOrWhiteSpace(clientUserAgent.Product.Name) || !Version.TryParse(clientUserAgent.Product.Version, out Version clientVersion)) + if (String.IsNullOrWhiteSpace(clientUserAgent.Product.Name) || !Version.TryParse(clientUserAgent.Product.Version, out var clientVersion)) throw new InvalidOperationException("Malformed client user agent!"); var apiUserAgentHeader = userAgentValues[1]; //make sure the api header matches ours - if (!ProductInfoHeaderValue.TryParse(apiUserAgentHeader, out ProductInfoHeaderValue apiUserAgent) || apiUserAgent.Product.Name != assemblyName.Name) + if (!ProductInfoHeaderValue.TryParse(apiUserAgentHeader, out var apiUserAgent) || apiUserAgent.Product.Name != assemblyName.Name) throw new InvalidOperationException("Missing API user agent!"); - if (!Version.TryParse(apiUserAgent.Product.Version, out Version ApiVersion)) + if (!Version.TryParse(apiUserAgent.Product.Version, out var apiVersion)) throw new InvalidOperationException("Malformed API version!"); + ApiVersion = apiVersion; + //check api version compatibility var ourVersion = assemblyName.Version; if (ourVersion.Major != ApiVersion.Major || ourVersion.Minor != ApiVersion.Minor || ourVersion.Build > ApiVersion.Build) @@ -152,10 +154,10 @@ namespace Tgstation.Server.Api if (String.IsNullOrEmpty(parameter)) throw new InvalidOperationException("Missing authentication parameter!"); - if(requestHeaders.Headers.TryGetValue(instanceIdHeader, out StringValues instanceIdValues)) + if(requestHeaders.Headers.TryGetValue(instanceIdHeader, out var instanceIdValues)) { var instanceIdString = instanceIdValues.FirstOrDefault(); - if (instanceIdString != default && Int64.TryParse(instanceIdString, out long instanceId)) + if (instanceIdString != default && Int64.TryParse(instanceIdString, out var instanceId)) InstanceId = instanceId; } @@ -166,7 +168,7 @@ namespace Tgstation.Server.Api break; case passwordAuthenticationScheme: Password = parameter; - var fail = !requestHeaders.Headers.TryGetValue(usernameHeader, out StringValues values); + var fail = !requestHeaders.Headers.TryGetValue(usernameHeader, out var values); if (!fail) { Username = values.FirstOrDefault(); @@ -193,6 +195,7 @@ namespace Tgstation.Server.Api Token = token; Username = username; Password = password; + ApiVersion = assemblyName.Version; } /// @@ -214,7 +217,7 @@ namespace Tgstation.Server.Api headers.Add(usernameHeader, Username); } headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent)); - headers.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue(assemblyName.Name, assemblyName.Version.ToString()))); + headers.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue(assemblyName.Name, ApiVersion.ToString()))); if(InstanceId.HasValue) headers.Add(instanceIdHeader, InstanceId.ToString()); } diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index c8c0b05bed..b963809ded 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,6 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release + 4.0.0.0 From ae16578ea6be3504bce95e5cbbfb4adf0f5ff025 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 16:50:54 -0400 Subject: [PATCH 393/397] Loggers are mandatory --- .../Controllers/AdministrationController.cs | 12 +++--------- .../Controllers/ApiController.cs | 11 ++++++++++- .../Controllers/ByondController.cs | 14 ++++---------- .../Controllers/ChatController.cs | 4 +++- .../Controllers/ConfigurationController.cs | 10 ++-------- .../Controllers/DreamDaemonController.cs | 4 +++- .../Controllers/DreamMakerController.cs | 4 +++- .../Controllers/HomeController.cs | 4 +++- .../Controllers/InstanceController.cs | 14 ++++---------- .../Controllers/InstanceUserController.cs | 13 +++---------- .../Controllers/InteropController.cs | 7 +++++-- .../Controllers/JobController.cs | 4 +++- .../Controllers/ModelController.cs | 4 +++- .../Controllers/RepositoryController.cs | 12 +++--------- .../Controllers/UserController.cs | 2 +- 15 files changed, 53 insertions(+), 66 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index 551dca9dc6..9b977a548a 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -50,11 +50,6 @@ namespace Tgstation.Server.Host.Controllers /// readonly IIOManager ioManager; - /// - /// The for the - /// - readonly ILogger logger; - /// /// The for the /// @@ -69,11 +64,10 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - /// The value of + /// The for the /// The containing value of - public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false) + public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false) { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.application = application ?? throw new ArgumentNullException(nameof(application)); @@ -83,7 +77,7 @@ namespace Tgstation.Server.Host.Controllers StatusCodeResult RateLimit(RateLimitExceededException exception) { - logger.LogWarning("Exceeded GitHub rate limit!"); + Logger.LogWarning("Exceeded GitHub rate limit!"); var secondsString = Math.Ceiling((exception.Reset - DateTimeOffset.Now).TotalSeconds).ToString(CultureInfo.InvariantCulture); Response.Headers.Add("Retry-After", new Microsoft.Extensions.Primitives.StringValues { }); return StatusCode(RateLimitHttpStatusCode); diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 803f3c29b5..c28a6620c0 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Globalization; @@ -38,6 +39,11 @@ namespace Tgstation.Server.Host.Controllers /// protected IAuthenticationContext AuthenticationContext { get; } + /// + /// The for the + /// + protected ILogger Logger { get; } + /// /// The for the operation /// @@ -110,12 +116,14 @@ namespace Tgstation.Server.Host.Controllers /// /// The value of /// The for the + /// The value of /// The value of - public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, bool requireInstance) + public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance) { DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); if (authenticationContextFactory == null) throw new ArgumentNullException(nameof(authenticationContextFactory)); + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); AuthenticationContext = authenticationContextFactory.CurrentAuthenticationContext; Instance = AuthenticationContext?.InstanceUser?.Instance; this.requireInstance = requireInstance; @@ -150,6 +158,7 @@ namespace Tgstation.Server.Host.Controllers return; } + Logger.LogInformation("Request made by User ID {0}. Api version: {1}. User-Agent: {2}", AuthenticationContext.User?.Id.ToString(CultureInfo.InvariantCulture) ?? "NULL", ApiHeaders.ApiVersion, ApiHeaders.UserAgent); await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } } diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index a137447c4e..2cb13a0652 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -30,11 +30,6 @@ namespace Tgstation.Server.Host.Controllers /// readonly IJobManager jobManager; - /// - /// The for the - /// - readonly ILogger logger; - /// /// Construct a /// @@ -42,12 +37,11 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The value of /// The value of - /// The value of - public ByondController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IJobManager jobManager, ILogger logger) : base(databaseContext, authenticationContextFactory, true) + /// The for the + public ByondController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IJobManager jobManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// @@ -85,12 +79,12 @@ namespace Tgstation.Server.Host.Controllers if (byondManager.InstalledVersions.Any(x => x == model.Version)) { - logger.LogInformation("User ID {0} changing instance ID {1} BYOND version to {2}", AuthenticationContext.User.Id, Instance.Id, installingVersion); + Logger.LogInformation("User ID {0} changing instance ID {1} BYOND version to {2}", AuthenticationContext.User.Id, Instance.Id, installingVersion); await byondManager.ChangeVersion(model.Version, cancellationToken).ConfigureAwait(false); } else { - logger.LogInformation("User ID {0} installing BYOND version to {2} on instance ID {1}", AuthenticationContext.User.Id, Instance.Id, installingVersion); + Logger.LogInformation("User ID {0} installing BYOND version to {2} on instance ID {1}", AuthenticationContext.User.Id, Instance.Id, installingVersion); //run the install through the job manager var job = new Models.Job { diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index f4347503d7..d6f66523c8 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; @@ -34,7 +35,8 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public ChatController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) + /// The for the + public ChatController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); } diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index e4d33d6e1b..04b226a30a 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -23,21 +23,15 @@ namespace Tgstation.Server.Host.Controllers /// readonly IInstanceManager instanceManager; - /// - /// The for the - /// - readonly ILogger logger; - /// /// Construct a /// /// The for the /// The for the /// The value of - /// The value of - public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, true) + /// The for the + public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 34d5be50c0..f91384a46f 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Linq.Expressions; @@ -39,7 +40,8 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The value of /// The value of - public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) + /// The for the + public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index e1b174c132..75801aac2d 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading; @@ -35,7 +36,8 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The value of /// The value of - public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true) + /// The for the + public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index b45b0b38dd..72b266e87d 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Net; @@ -49,7 +50,8 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache) : base(databaseContext, authenticationContextFactory, false) + /// The for the + public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, false) { this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 9fabc92699..9435a6786e 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -45,11 +45,6 @@ namespace Tgstation.Server.Host.Controllers /// readonly IApplication application; - /// - /// The for the - /// - readonly ILogger logger; - /// /// Construct a /// @@ -59,14 +54,13 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - /// The value of - public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager, IApplication application, ILogger logger) : base(databaseContext, authenticationContextFactory, false) + /// The for the + public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager, IApplication application, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, false) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.application = application ?? throw new ArgumentNullException(nameof(application)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } void NormalizeModelPath(Api.Models.Instance model, out string absolutePath) @@ -169,7 +163,7 @@ namespace Tgstation.Server.Host.Controllers return Conflict(new { message = e.Message }); } - logger.LogInformation("{0} created instance {1}: {2}", AuthenticationContext.User.Name, newInstance.Name, newInstance.Id); + Logger.LogInformation("{0} created instance {1}: {2}", AuthenticationContext.User.Name, newInstance.Name, newInstance.Id); return Json(newInstance.ToApi()); } @@ -289,7 +283,7 @@ namespace Tgstation.Server.Host.Controllers } catch (Exception e) { - logger.LogError("Error changing instance online state! Exception: {0}", e); + Logger.LogError("Error changing instance online state! Exception: {0}", e); originalModel.Online = originalOnline; originalModel.DreamDaemonSettings.AutoStart = oldAutoStart; if (originalModelPath != null) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs index adc0d9d4cc..a76407212f 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs @@ -21,21 +21,14 @@ namespace Tgstation.Server.Host.Controllers [Route("/" + nameof(Models.InstanceUser))] public sealed class InstanceUserController : ModelController { - /// - /// The for the - /// - readonly ILogger logger; - /// /// Construct a /// /// The for the /// The for the - /// The value of - public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, true) //false instance requirement, we handle this ourself - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } + /// The for the + public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) //false instance requirement, we handle this ourself + { } /// /// Checks a for errors diff --git a/src/Tgstation.Server.Host/Controllers/InteropController.cs b/src/Tgstation.Server.Host/Controllers/InteropController.cs index d145f99632..f7b7b808e9 100644 --- a/src/Tgstation.Server.Host/Controllers/InteropController.cs +++ b/src/Tgstation.Server.Host/Controllers/InteropController.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Components; @@ -24,9 +26,10 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public InteropController(IInstanceManager instanceManager, IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory, false) + /// The for the + public InteropController(IInstanceManager instanceManager, IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, false) { - this.instanceManager = instanceManager; + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); } /// diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 1fee01abd5..16f628cba4 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Net; @@ -28,7 +29,8 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// The for the /// The value of - public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory, true) + /// The for the + public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); } diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index c9ba839646..dbc2c704d4 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -24,8 +25,9 @@ namespace Tgstation.Server.Host.Controllers /// /// The for the /// The for the + /// The for the /// If the requires an - public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, bool requireInstance) : base(databaseContext, authenticationContextFactory, requireInstance) { } + public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance) : base(databaseContext, authenticationContextFactory, logger, requireInstance) { } /// /// Attempt to create a diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 56de5ba872..1d1ef12163 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -40,11 +40,6 @@ namespace Tgstation.Server.Host.Controllers /// readonly IJobManager jobManager; - /// - /// The for the - /// - readonly ILogger logger; - /// /// Construct a /// @@ -53,13 +48,12 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - /// The value of - public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, Octokit.IGitHubClient gitHubClient, IJobManager jobManager, ILogger logger) : base(databaseContext, authenticationContextFactory, true) + /// The for the + public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, Octokit.IGitHubClient gitHubClient, IJobManager jobManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } static string GetAccessString(Api.Models.Internal.RepositorySettings repositorySettings) => repositorySettings.AccessUser != null ? String.Concat(repositorySettings.AccessUser, '@', repositorySettings.AccessToken) : null; @@ -190,7 +184,7 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); - logger.LogInformation("Instance {0} repository delete initiated by user {1}", Instance.Id, AuthenticationContext.User.Id); + Logger.LogInformation("Instance {0} repository delete initiated by user {1}", Instance.Id, AuthenticationContext.User.Id); var job = new Models.Job { diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 67161f1db1..615a046902 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of - public UserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory, false) + public UserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, false) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); From 913d91464d5bbf34a5dad5372506ed30e07d27fc Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 16:54:28 -0400 Subject: [PATCH 394/397] Instance manager is now responsible for job manager Prevents race conditions --- .../Components/InstanceManager.cs | 16 ++++++++++++++-- src/Tgstation.Server.Host/Core/Application.cs | 6 ++---- src/Tgstation.Server.Host/Core/IJobManager.cs | 3 ++- src/Tgstation.Server.Host/Core/JobManager.cs | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 323fa7568a..8c395077c3 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -41,6 +41,11 @@ namespace Tgstation.Server.Host.Components /// readonly IApplication application; + /// + /// The for the + /// + readonly IJobManager jobManager; + /// /// The for the /// @@ -62,13 +67,15 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// The value of /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILogger logger) + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, ILogger logger) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); instances = new Dictionary(); @@ -161,6 +168,7 @@ namespace Tgstation.Server.Host.Components try { await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); + await jobManager.StartAsync(cancellationToken).ConfigureAwait(false); var dbInstances = databaseContext.Instances.Where(x => x.Online.Value) .Include(x => x.RepositorySettings) .Include(x => x.ChatSettings) @@ -179,7 +187,11 @@ namespace Tgstation.Server.Host.Components }); /// - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))); + public async Task StopAsync(CancellationToken cancellationToken) + { + await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false); + await jobManager.StopAsync(cancellationToken).ConfigureAwait(false); + } /// public IInteropContext Register(string accessIdentifier, IInteropConsumer consumer) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 9674267ce9..78abc75d9c 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -215,11 +215,9 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); - - services.AddSingleton(); - services.AddSingleton(x => x.GetRequiredService()); - services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(ioManager); services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 35f8d5c56e..beaa6010eb 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using System; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ namespace Tgstation.Server.Host.Core /// /// Manages the runtime of s /// - public interface IJobManager + public interface IJobManager : IHostedService { /// /// Get the for a job diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 88314172f0..a678d47efe 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -10,7 +10,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { /// - sealed class JobManager : IHostedService, IJobManager, IDisposable + sealed class JobManager : IJobManager, IDisposable { /// /// The for the From b15be2cad9ba689c31eb7357da6f2cb902cd6e20 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 1 Aug 2018 17:00:15 -0400 Subject: [PATCH 395/397] Remove erroneous database initialize call --- src/Tgstation.Server.Host/Core/JobManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index a678d47efe..c059cdb7ed 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -146,7 +146,6 @@ namespace Tgstation.Server.Host.Core using (var scope = serviceProvider.CreateScope()) { var databaseContext = scope.ServiceProvider.GetRequiredService(); - await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); //mark all jobs as cancelled var enumerator = await databaseContext.Jobs.Where(y => !y.Cancelled.Value && !y.StoppedAt.HasValue).Select(y => y.Id).ToAsyncEnumerable().ToList(cancellationToken).ConfigureAwait(false); From a98afcecae8ade1a379841e107de5150a60f9ef9 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 1 Aug 2018 18:17:48 -0400 Subject: [PATCH 396/397] Nani? this was included? --- UpgradeLog.htm | Bin 41384 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 UpgradeLog.htm diff --git a/UpgradeLog.htm b/UpgradeLog.htm deleted file mode 100644 index 1b7f8648c7794d8114bd8e89b04958735798ae8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41384 zcmeI5`BNLolE?ekw-Nh4Xw1GbvmOqC&Ea|WgaCmthq(;4#}h9w;y#TyJ@ek5zPq1q zCFSaFiI!Rj?1@zj%FN2FZvF57{3H4ynuy*+?4jLUhjALG&W(j{5l&qo>hN)ceHUbKpFW zcB3PXo76hu_ly>TX0QuD_q`ROE7$80{XFNa%5~L2e&qT%qO=-qP=AxV>zwy++@(aA zRQYZJy@zYz_57ywS@ej$)XOGqD(?Ky;js=Ts%zNk_XTw~x$dFAD(6Otv*?79&FC|w zyNqA8#uM#&qFG=cMcdR^=XcY!?Q7VVsB%TWeP3zti^FLLD5qc`YP|sF86)d~vO~FW zT8XZ~JdVp5_@6=1O?vq!$79An4fn%{#9j5g>&Cal)d^>(wCbmC&$9yUlb<QoCN8Gz@>;7yUOa#=x}#1WBrg^fOmmZVUy= zBn1V&;`T21>Z-!s4X7-sP05L+x$uivw?w!bg!f zwL-leN;SWAJ>__^$*i;MRjyANvu2x>NGC~S^zhZaUHWQm{3k%F@R7dSc3Aee2uawV z0z;5A+PrZ7<@lFJnoRy*K>>|HbSTnqau!IZdEG58hxs6Ug{w+}hR8b$cIYz?&aKUW zS}ql#?+JQTbhcwC@n@|`M37%;#4k`!+#Fz>Cs3+2nDu6`*{t~7E7Obrw%U()Y_ibf ztnnE*n`JDx5r|@4ShC{HcYteI(F4X>VRZR4u-8y&EiwRCifASsTN}w;XOqV)aPtyQ z%3jIy5J!&~^{LZ22RU(U$4oW4$L&?^OnrohX14hP%r~P;=Ltyj$rn3j9I|Yh)0*QO zT$>fHQj&ij4?Ms;4%~>(tztBChq)DCwvWSJdg|$&XJ7SBAqwZ;M#|eEiGxmlvL&&K z*YWAb+Q#Kf)>`-EpBJzvlDrcn@;KVNkx0!mc>%Kc`DJqOL1nW2@)Kk;Desb-R&7bU zG~<2Cb~85b3|TQxPT0sZm*<#oF10!bw3;*PE6F|Jqm`enp0u`=Z?x|uTK=r$H(0s+ zvb_EPk{s}pee1#k529HIu=M2zqNzOjPtZ=(3Vx`!NBMkpPND z?maDijHU-ZNfT_N0W_y1*@`!{#-RXJ9=f<5kDYj*RJx`$_(1~NZ^>${#UOH})x^-v zF?pSD1o?G%Qr;>Lpk2)qmyf;Sq#`XFT4$ZK$hpSU^`533^HJAJb*<+h4z>m>uGJ*D zS{+Lo!dg-m=8n_MMeWT)e~dN@Yv0F| zd(EV{zC}sp4Ou&tS(L4SAFueMuC)I2apU~nP_M(+XgPaB$@{k$wf@mvW1Y84XBpYI z&qRwe%=4tj#dF$cE%Hl$J#IZmg1rQM3(mFb*?L`gJLe|RD*BA_X-zkNKb$kYHO_q1 zthQK@$4;6g#GHI2*m5PFvjyh3MV_QtHh`dYlFow$lVl3Ik0xuaU*%&tG4q+EDWp+U z*EEb^C?1e+P0;H`q3l_FOp0&czYKc1rR~0`;;EZYUh>aGeFyg(TOQWL%Kn-c9ADen3Q1Vxjq3EK@D?W}Rrk-qOJupiICq>c zuPi}-^!J=|yj7kpK*Nw{GP~__dBM{9x#V+_>u$B-=dE#F8JAY(PM4c~CaL<(H}n2* z{P`QLn)}pxv>{J3aN{c$iB0(2jh*GqKeWmXKZCrU^IFbkfsbG)Pp@$(-flWio>h<{ zX;58BqQp_n$aUuhhdT0;wzxBjxB7RE`E_0w5RzGdIF=7RYu3>h@>KI_IxjOupPm>E z<;xEfN7p>eCy{sMeNs0Xf4zp&mG`Wr`!2b+sn`3p^)j-!!N>fKGV@eW6N=d3s#6fjmD zjaZQu+jo;!zj?Vf+Wy8I(dQlT4jAz&kQIBbvQ$ zBYu914(+0~c4$e4CH0b z`W9-RTLMr;B8TP4ZFI(y6Gta2rB9Uc& zw{ObEzTPcGi>z5o?6&pwx6s>t_=P)POUB{y7@S$-ICoR3EQYmsgN`rJ=PEs4LdQ=x zZF;B_)neqItd-jzSllT>(j0kATe-7(Zc*%RsrN z_872BTyJx>4g4vt6savyW1s%D*L98aS!8aOI?_fJ>YdVhj(rLhMmJ3li|m75W1LU9 za|ER}z-5074Sklu;M}#@ru_jl z5In_f-q6=FqdTOpL7=?lIK-VrV9#(q%Q!Z{_ch$V;8#09M1dz@S#o@PO|2F1oB=QS z8fETX(B}p8mqxn+hW3hl0;6eY^&Sj1sWT2e6eWHKj%D!O0>5K$SOK33y$=ERHGL@G z-N&6BN>*r7q*u*?EpYrq9qn*E_eC?pTrPmVInujZn??cob zhr08O?iHniv`(u-Xi$c#7r<1!Q2PZmE6eo14aU>pH%!?I^{%1R7JS+UhYju>)0h0m z1Gsn$)^kAFW2{F|`3u*Qk`;Q`qNEIs78u(ZH0cMv|484gI(;<5cr@!I?Jt}r&13sp z_2#`-ZM*2ar*%Jmj_)gJ(ejMaA{gGfHhb)NIDd5y$4gF9N;{s8Jh_#Ny}XE~UqYu2qxmk;k6+Nx$LPd8XB}tIz7;gx5!$=L_nbRR=wCg_G(zh+ zEJcxWc_B}LbwRBvW$TQ0k-o>!(i>cl1Ah&TI_|8FwEZG5H_*wGz&ztj{`Df7T3D=8 zcOI>*XplUBeP=^P@fYTRGenvE;!U7wEutrxX2IYMcyDl2taFmHD@HJh-rjVh8ppEF z@coQkkY_kTzp|S0)MQ;(xn7{n27Ml23)JU4BORcX#&Jd2r0a8;*78%TuBKqEG1_-v zG78jD?(~7v1!vnp(H>}dKNFm7xR%?{c!}2XbGGPfk5*%}c?JBUyCbT8hMx0`U=A4C z?Xb>$+57`2Izu0e&LU1@6Lno>Oj;XGLDf-4GwN)0KYe}x_CB>0VeJFc5o#@QeMy@| zN9h4d2Ej}o_5gNt6+GoResN{*xi=5A19&zLv=5B!3HRioj5x~3`fr2hAtgn|Sq7JD zxbX!nw5plmT;;O*;=Fcb$#a{g&6Sg?3(B@QyXLOAybAT6f%~wNvKh*U8P}r2XqoYUE-)H#4uMSbnYVdM^-{weT{pHZY^RB*3j1P zoc5R2e}~nW2B&LUKI8bAUIyv?jJjH>zM-`uCR(p667!6jAAm53-XG^P!~OR_(Oh11 z7WOIjvCIfQqy1+&o@1ddu_6=Tbn-b+ufTCy5w6b3X6L}+5 zXts|9QEXrVoGySf#MrdjKB1N{Rg7hbI$AyVfzdu!f;U7T%Zx_Rk0aomyAeO5f91pF zL5$G4kK-DxXMm`(SG3R_`3oz+ctWr8e1zv2Bi{qgHRCyRmav~|MfbH{FEN@GU_YS; zt$z2o?&CN}eMK-8wK)PK-I-*ZeL$XsQcwB3=Kd=8BwM1Tw~k}nS)#==sCo)c;-o$= z9&p7QGC%iY8w32L9_*}hl|%QDcgb)klJ8^iK9?F~r(+YpolR6UI=#02j8Mc$(0M$pP7wDcvvT2l<5E6=H?2z3elsqKt1jo^w6VRR#P$ zbN+#n&)7q)7iXwb<@klZUsI~M`4GDJh?a^wFN5n8I(v#9%UtcErN8hw0HY%1vy4Nl zM%ltuR*0v-_(av-g7+)UL#;x0{RDFD%+qvLCPXe zJ4>To5=F)kMyh4k4`X1vWsvOXt zZ<(G?bxv_zkjwO zo##)tx6HrgzxA6n7SY_El#h>JaX$GHA$Re@;hik)CZke_{@|O$OnP2#C9?5$!?E<9 zvPUi#Q;7c6V82`z=AuG(f>OPVMelV`97~$kQ~ak)3N+4t#JO=>AKlSoxOK$Hjq8sc z-!u<`RHdn^%zoAgE&p`ejN7CyMF-?@%L}Zfh4$43DkxJZe8iPacrM;$^`4(D+5L$^ zd8ngZqrMo-c#OLt3}ON+iz)mhh*gyiT%V^vs+p_ z>>Tp@LA66jI!Y}yOQidPUQ^Uu5nairo|cdv)NUMUgKWIz2_^N9G@!?G1sJv$dFiJ2 zF|eDzf!zJIkGsx$J>_BAQBrt-efMLPHmsEedDmymxVZMK;vdM)YgH03d`!Jl=0hW? zwz==^MK@G@fmfp)!Ml9_!8eJSo=DRCmbbWbQ|}2rQ8p#4m&WtmYa&k-MVM@VnXHT- zT>%`^c>M;xzq5GFvQqDX-v+*c6uG(87^faaR#kC4Yqsh`{o}M~p+fA#PT_^i)79O>l%RE}W zQ$ty~W4AugjvMV+jPu*HOI&+p>&Ry2tDmV!s+(kW^jCeIkTvhxufIHp>dm{`^=S6I zQ@gzL{~`2f0ZlAjTszkL)38a&4|>bqld3tX{SxvnG#m9Uv2JIZwBJD1IeQMqwT_)@ zpR?AGivGwGQjS9Jt!N%jw(e<0ds&?}V+HCx?dNq$z>k#b-^j=}y>#XN7 z`WQiaRjc)ZsBMaeIWT3#G(Vvv}^ObL+dG7BeJ-_V!jr;m-78BnhE!{TS=B3+YCyU0urvgzVJq4|3 znmn}d_F?f|ywjVA#WCZz)`}`ga@*fa%86g0=*B4wNI# z)((Mx&C#vCMD?a-cDK{02gFiMcSr^vv;NVur8}5hs+u5+&SM4{H#$`@9{=7-zsk?%L1Wr`-qwd+KsTl>UHzJQ?OtQdBoT3~)Qok@=8qs| z@ksac#J3(HlX1S;%5j`IqWp=ytN#fw>r=X};8dsb_V`m~1!K|9}7+X$y@jgxiPvhyLw zuKc|IamBQ?uC%X0yZd!+@6^fr?j{O^o`W6m~Z(mR*T`eD9JKREU+y9@9 z-W#9B^Nq^@pE}u6WM>5!_WvqsmA-3q7^vH4`b{&|t8%-RhZ3HWKRfQYqyEpu&MYgI z#%{ssdGA++oL1a&s-I*%Vc7_-vf$d8>anYZXf+zc3VY|O8Svt@8pBNPQ_q5%uGtuN z(kf=bPS=dX7KJ@8{ZV>LPe7T6B{{6!Usy&i!~RR{Ua@~Y(sR|43`K3F*OZ^rb$a#M z1&yb$A9_EF{>4S|B|kvfdP&0VcZI#yx;mzN(P*rHJKhfEV@40XE>fcBy)}Y8*HavB znmRr%H4gSRTjf(%+&J=$JV=*lP;@=}vEBuPm&8`$bh`_zWE7&yjj}%Ue6p2%Q_9`t zOBq4DucEb9ra8~;?jvPnrEDt9`iw+09zW2x5^?tkO zrYwhaq-Lc*&%?!dn+_j6|NQac_^9WsOL%Si7q^#0bp2HxUM5?0xaqm*j}OO9d0*T3 zRvWB{#LxJ39*(Blb$IG|>5mV`(`7WR-rW;+H&1h}e2ikJFY&-{zn_Qajo@@-!1LZ8 zA5I3&nAOH@JIlk@(4xaZ&pCg5I1Vb0rFm*x3|DzLnQYbJrstwRJ{&hMct%S9C2Iey z9Io@)KbPRb)K`&f;h)ZXBqQ%DUvZ81ubz|jG|PSeK^AOmaax9Gv5g_* z-EJSbALs4*F+fenU_~Lt@#8q$-cx`RIIGcw__yPIE69bOSJ8hrYB#QQN6@L-W4YtX{Ig-o^rm-#D1U3u1>62`?TNGZHH$0KN`j-3 z@D^=77p3RKDro8g^I2CJSuTd|M5ma{kK+6DK5r@4plboBoYMRqLe zxhMTYUQ)Aodc0u&=ODH9RzAHAVSo(j2oa_cN_uz#e1tu+qpqw7)MuROdGrx}2YL2P z@8Fr>{sf;|OX(#q%qX43wMSaElTrU5#io0bchz4rIw{#It_S`~ddPI;?K(s`JxOd@ zgT%9h&CdQ>t*ZA4CN*aD=6OYFw9FP{ z$!9G5_)StmE}|L9vDzJp30dhR7qjeew!|1^zYj_AnT@oz@#W3x8DGC6FYT8XPuuT9 zVnY5TEi>Uxvr-dUbmXS}aucZgeMn9`Pm`9N_`PPOC*GzbKkb*FIDNkl395}8#Is9D zZzrp6vl5mCqa$(cm$)o?e;*R)b6ZJS3t6jXJZr1nk)QUROyElo+$P8q3dmxobaQ`}OP$lgq*Jj#)yuI+JFW-))X z+)GJ5p4xLb?aj0)PvT{<{=R{`65MdZVoNjejPar8gU^ zhB8yzoCgeho67!}3^dQhe0s(|Z(S=n51Bjh5|q|69=rd)dd5@We)UW`!>Iq`&=a2G SOY%JaB&srrXUO#d_5Kfj4hm2J From 26f204ad88c9fdd96c0cb68473ec41b3a8c7ddbc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 1 Aug 2018 18:19:12 -0400 Subject: [PATCH 397/397] Fix up todo list --- v4_prototype_TODO.txt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index d44c7646f5..afe9140947 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -3,21 +3,16 @@ Watchdog performance counters Install as network service instead of LocalSystem if win10 >= Creators update to allow for unprivileged symlinks -Nuspec stuff for api and client - Verify the byond cache folder location on linux -Server updates. Use FOLDERS so dependencies can be properly packaged --Fix this - Don't throw arg null exceptions with null [FromBody]'s, apparently that's allowed, return bad request instead -Test repo +Test repo more Test byond Test compile Test watchdog test configuration -Think about how to preserve the LastOriginCommit while merging/test merging +Client library -Rewrite the autoupdater to use a process \ No newline at end of file +Control panel \ No newline at end of file