Create PSForever config framework (#283)

We can now parse arbitrary INI configuration entries. This will allow
server customization and testing without recompiling the server.
This commit is contained in:
pschord 2019-10-21 14:12:26 -04:00 committed by GitHub
parent 4b71d76cb2
commit c3d19b5377
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 647 additions and 5 deletions

View file

@ -11,6 +11,7 @@ import ch.qos.logback.core.joran.spi.JoranException
import ch.qos.logback.core.status._
import ch.qos.logback.core.util.StatusPrinter
import com.typesafe.config.ConfigFactory
import net.psforever.config.{Valid, Invalid}
import net.psforever.crypto.CryptoInterface
import net.psforever.objects.zones._
import net.psforever.objects.guid.TaskResolver
@ -102,6 +103,33 @@ object PsLogin {
}
}
def loadConfig(configDirectory : String) = {
val worldConfigFile = configDirectory + File.separator + "worldserver.ini"
// For fallback when no user-specific config file has been created
val worldDefaultConfigFile = configDirectory + File.separator + "worldserver.ini.dist"
val worldConfigToLoad = if ((new File(worldConfigFile)).exists()) {
worldConfigFile
} else if ((new File(worldDefaultConfigFile)).exists()) {
println("WARNING: loading the default worldserver.ini.dist config file")
println("WARNING: Please create a worldserver.ini file to override server defaults")
worldDefaultConfigFile
} else {
println("FATAL: unable to load any worldserver.ini file")
sys.exit(1)
}
WorldConfig.Load(worldConfigToLoad) match {
case Valid =>
println("Loaded world config from " + worldConfigFile)
case i : Invalid =>
println("FATAL: Error loading config from " + worldConfigFile)
println(WorldConfig.FormatErrors(i).mkString("\n"))
sys.exit(1)
}
}
def parseArgs(args : Array[String]) : Unit = {
if(args.length == 1) {
LoginConfig.serverIpAddress = InetAddress.getByName(args{0})
@ -125,9 +153,15 @@ object PsLogin {
configDirectory = System.getProperty("prog.home") + File.separator + "config"
}
initializeLogging(configDirectory + File.separator + "logback.xml")
parseArgs(this.args)
val loggingConfigFile = configDirectory + File.separator + "logback.xml"
loadConfig(configDirectory)
println(s"Initializing logging from ${loggingConfigFile}...")
initializeLogging(loggingConfigFile)
/** Initialize the PSCrypto native library
*
* PSCrypto provides PlanetSide specific crypto that is required to communicate with it.
@ -194,9 +228,8 @@ object PsLogin {
SessionPipeline("world-session-", Props[WorldSessionActor])
)
val loginServerPort = 51000
val worldServerPort = 51001
val loginServerPort = WorldConfig.Get[Int]("loginserver.ListeningPort")
val worldServerPort = WorldConfig.Get[Int]("worldserver.ListeningPort")
// Uncomment for network simulation
// TODO: make this config or command flag

View file

@ -0,0 +1,28 @@
// Copyright (c) 2019 PSForever
import net.psforever.config._
object WorldConfig extends ConfigParser {
protected var config_map : Map[String, Any] = Map()
protected val config_template = Seq(
ConfigSection("loginserver",
ConfigEntryInt("ListeningPort", 51000, Constraints.min(1), Constraints.max(65535))
),
ConfigSection("worldserver",
ConfigEntryInt("ListeningPort", 51001, Constraints.min(1), Constraints.max(65535))
)
)
override def postParseChecks : ValidationResult = {
var errors : Invalid = Invalid("")
if (Get[Int]("worldserver.ListeningPort") == Get[Int]("loginserver.ListeningPort"))
errors = errors ++ Invalid("worldserver.ListeningPort must be different from loginserver.ListeningPort")
if (errors.errors.length > 1)
// drop the first error using tail (it was a placeholder)
Invalid(errors.errors.tail)
else
Valid
}
}

View file

@ -0,0 +1,19 @@
# This is a comment
[default]
string = a string
string_quoted = "a string"
int = 31
time = 1 second
time2 = 100 milliseconds
float = 0.1
bool_true = yes
bool_false = no
# missing
[bad]
bad_int = not a number
bad_time = 10
bad_float = A
bad_bool = dunno
bad_int_range = -1
bad_int_range2 = 3

View file

@ -0,0 +1,79 @@
// Copyright (c) 2019 PSForever
import scala.io.Source
import org.specs2.mutable._
import net.psforever.config._
import scala.concurrent.duration._
class ConfigTest extends Specification {
val testConfig = getClass.getResource("/testconfig.ini").getPath
"WorldConfig" should {
"have no errors" in {
WorldConfig.Load("config/worldserver.ini.dist") mustEqual Valid
}
}
"TestConfig" should {
"parse" in {
TestConfig.Load(testConfig) mustEqual Valid
TestConfig.Get[String]("default.string") mustEqual "a string"
TestConfig.Get[String]("default.string_quoted") mustEqual "a string"
TestConfig.Get[Int]("default.int") mustEqual 31
TestConfig.Get[Duration]("default.time") mustEqual (1 second)
TestConfig.Get[Duration]("default.time2") mustEqual (100 milliseconds)
TestConfig.Get[Float]("default.float") mustEqual 0.1f
TestConfig.Get[Boolean]("default.bool_true") mustEqual true
TestConfig.Get[Boolean]("default.bool_false") mustEqual false
TestConfig.Get[Int]("default.missing") mustEqual 1337
}
}
"TestBadConfig" should {
"not parse" in {
val error = TestBadConfig.Load(testConfig).asInstanceOf[Invalid]
val check_errors = List(
ValidationError("bad.bad_int: value format error (expected: Int)"),
ValidationError("bad.bad_time: value format error (expected: Time)"),
ValidationError("bad.bad_float: value format error (expected: Float)"),
ValidationError("bad.bad_bool: value format error (expected: Bool)"),
ValidationError("bad.bad_int_range: error.min", 0),
ValidationError("bad.bad_int_range2: error.max", 2)
)
error.errors mustEqual check_errors
}
}
}
object TestConfig extends ConfigParser {
protected var config_map : Map[String, Any] = Map()
protected val config_template = Seq(
ConfigSection("default",
ConfigEntryString("string", ""),
ConfigEntryString("string_quoted", ""),
ConfigEntryInt("int", 0),
ConfigEntryTime("time", 0 seconds),
ConfigEntryTime("time2", 0 seconds),
ConfigEntryFloat("float", 0.0f),
ConfigEntryBool("bool_true", false),
ConfigEntryBool("bool_false", true),
ConfigEntryInt("missing", 1337)
)
)
}
object TestBadConfig extends ConfigParser {
protected var config_map : Map[String, Any] = Map()
protected val config_template = Seq(
ConfigSection("bad",
ConfigEntryInt("bad_int", 0),
ConfigEntryTime("bad_time", 0 seconds),
ConfigEntryFloat("bad_float", 0.0f),
ConfigEntryBool("bad_bool", false),
ConfigEntryInt("bad_int_range", 0, Constraints.min(0)),
ConfigEntryInt("bad_int_range2", 0, Constraints.min(0), Constraints.max(2))
)
)
}