2.2.3 Cycle 3 - Configuration Handler
Design
Objectives
For this cycle the main objective is to turn the node software into something more aligned with what a user would actually be able to use, as although it will mainly be computer oriented people using this part of the project having it easy to understand is still a big benefit.
Usability Features
Configuration handler
For the configuration handler it will originally start of very simply and will probably be somewhat a pain to use, but the hope is that by laying out a ground framework now that contains the console interface that will be used to interact with the configuration handler to be in plain, easy to understand English. This will give users the best chance at being able to run through the configuration setup with as little friction as possible.
Plain, easy to read English text based interface.
Allows user to respond in a variety of ways ("yes", "y","confirm","approve", etc).
Key Variables
user_config
to store the user's current configuration settings for various parts of the system. (which port to use for the server, etc)
config_path
the default place for user configurations to be stored by the software for loading/saving configs.
Pseudocode
The handler will be built with three main functions:
get_config
meant for collecting the configuration for use by other parts of the program.create_configuration
meant for generating a new configuration for the user if they don't already have one.save_config
meant for saving the current version of the configuration to the file.
Getting the configuration.
get_config
will allow any part of the software to load the newest version of the configuration, ideally this will be from memory but will likely just be from the file to start with. It will look something like the following:
// Psuedocode - get configuration function
CONST config_path = "./monochain/node.config" // the key variable described earlier.
FUNCTION get_config():
// load the config from the defualt file location and decode with json.
user_config = file.read(config_path, "json")
// check if the config loaded properly
IF user_config DOES NOT EXIST:
// if it hasn't, let the user know and start generating a new one.
OUTPUT "No config detected, or error occoured."
user_config = new_config()
END IF
// if generating a new config failed aswell, then exit with code 100
IF !user_config.loaded
OUTPUT "Failed to create a new configuration file.\nExiting..."
exit(100)
ENDIF
// if we make it to this part of the code then the config is ok
OUTPUT "\nConfig Loaded..."
return user_config
END FUNCTION
Generating a new config
create_configuration
is a pretty simple function that collects data from other functions - such as the ask_for_port
one also in the pseudocode below - and formats that data into a configuration object before saving it and returning the config to wherever it has been called from.
// Psuedocode - create a new configuration
FUNCTION create_configuration():
// create the config object
config = {
// let's the program know the config loaded successfully
loaded: true
// generate the port to use in the function below
port: ask_for_port()
}
// save the config object to a file
save_config(config)
return config
END FUNCTION
FUNCTION ask_for_port():
// ask the user for the port
OUTPUT "What port would you like to run your node on?"
port = INPUT
// check the port is valid
IF port > 65535 || port < 1:
// if it's not valid, tell the user and ask for the port again
OUTPUT "That port does not exist! You might want to enter a number between 1 and 65535."
return ask_for_port()
END IF
// return the port
return port
END FUNCTION
Saving the configuration
This function simply saves the configuration to a file so that it can be loaded/updated by other parts of the program as needed, and will keep its contents if the node/computer running the program is shut down or stopped.
The code will look something like the following:
// Psuedocode - save the configuration
FUNCTION save_config(config)
// convert the data into something safe to be saved into a file
data = json.encode(config)
// write the encoded data to the disk at the path ./node.config"
file.write("./node.config", data)
END FUNCTION
Development
Most of the development for this cycle went pretty smoothly, which was very nice!
This was mainly due to the module being relatively simple, so most of the development was simply converting the pseudocode and concept specified above into V code.
In order to do this I first created the "configuration" module, which is done simply by creating a new folder with the name of the module and adding the line module configuration
to the top of any files that are part of that module.
Once the module was setup I created three files for the code to help separate it out and make it easier for anyone looking at the code base to see what was going on, the files were:
configHandler.v
- This is the main file of the module and contains theget_config()
function mentioned earlier, this then calls on a function from theconfigFileHandler.v
file below and if there is a config file setup, loads it, and if not then calls on theconfigCreator.v
file to create a new configuration.configCreator.v
- This just houses some basic user interfacing functions that ask for various pieces of data required to generate a new configuration and then turns that into a new configuration object and saves it using theconfigFileHandler.v
file below.configFileHandler.v
- This is a basic file used to load, save, and check the existence of configuration files, any file read through this is type safe due to the language being used (VLang) reading json data using the expected object type and rejecting the file if it is incorrectly formatted. This means that all the data loaded to and from files using this part of the module are the correct object types and shouldn't cause any formatting crashes in other parts of the program.
Outcome
Because there is actually quite a lot of code in this module, I will just include some major functions such as the ones mentioned in the pseudocode.
Getting the config file
// Vlang Code - "./packages/node/src/configuration/configHandler.v"
pub fn get_config() UserConfig {
// the function used below can be found in the configFileHandler file
// it is also in the final code subsection further down this page
mut user_config := load_config()
if !user_config.loaded {
println("No config detected, or error occoured.")
// the following function asks the user if they want to generate
// a new config and if so takes them to the "create_configuration" function.
user_config = new_config(0)
}
if !user_config.loaded {
// if something went wrong, quit
eprintln("Failed to create a new configuration file, unexpected value was $user_config ")
exit(100)
}
// nothing went wrong! So load the next part of the program.
println("\nConfig Loaded, launching API server...")
return user_config
}
This version of the code can be found on Github Commit bc45df98e7.
Generating a new config
// Vlang Code - "./packages/node/src/configuration/configCreator.v"
pub fn create_configuration() UserConfig {
// build the config object using subfunctions
config := UserConfig{
loaded: true
port: ask_for_port(0)
}
// save the config
save_config(config, 0)
return config
}
fn ask_for_port(recursion_depth int) int {
// this just asks the user what port they'd like to run their node on
// then sends that data back to the config creator.
mut port := (read_line("What port would you like to run your node on (default: 8001)?\n$:") or {
eprintln("Input failed, please try again")
utils.recursion_check(recursion_depth, 2)
return ask_for_port(recursion_depth + 1)
}).int()
if port > 65535 || port < 1 {
eprintln("That port does not exist! You might want to enter a number between 1 and 65535.")
utils.recursion_check(recursion_depth, 3)
return ask_for_port(recursion_depth + 1)
}
return port
}
This version of the code can be found on Github Commit bc45df98e7.
Saving the configuration files
This includes the save_config
function as expected, but also the load_config
function which was originally planned to just be built into the get_config
function from earlier.
// Vlang Code - "./packages/node/src/configuration/configFileHandler.v"
pub fn save_config(config UserConfig, recursion_depth int) bool {
// setup a failure tracker
mut failed := false
// encode the data
data := json.encode(config)
// this shouldn't be here but it is in the commit referenced so I left it in.
// it doesn't error, it'll just never get run.
if failed {return false}
// save the file
os.write_file("./node.config", data) or {
// if this gets run then something went wrong.
eprintln('Failed to save file, trying again.')
if recursion_depth >= 5 {
// if it's already failed multiple times then quit
eprintln("Failed to save file too many times, continuing with program but your config won't be saved")
failed = true
} else {
// if no recursion issues, then just try again
failed = !save_config(config, recursion_depth + 1)
}
}
if !failed {
println("Sucessfully saved configuration file.")
}
return !failed
}
pub fn load_config() UserConfig {
if os.exists("./node.config") {
// config file already exists, make sure we can open and decode it
config_raw := os.read_file("node.config") or {
// something went wrong opening the file, return null
eprintln('Failed to open config file, error $err')
return UserConfig{ loaded: false }
}
user_config := json.decode(UserConfig, config_raw) or {
// something went wrong decoding the file, return null
eprintln('Failed to decode json, error: $err')
return UserConfig{ loaded: false }
}
return user_config
}
return UserConfig{ loaded: false }
}
This version of the code can be found on Github Commit bc45df98e7.
Testing
Tests
1
Create a new configuration file using "create_configuration()"
Console logs to confirm the operation is commencing and a configuration file to be generated.
As Expected
Pass
2
Load the new config file using "load_config()"
Config file should be loaded and returned
As Expected
Pass
3
Load the new config file using "get_config()"
Config file should be loaded and returned
As Expected
Pass
Evidence



Last updated