#[cfg(all(not(target_env = "musl"), feature = "jemalloc"))]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
use anyhow::Result;
use clap::{Parser, Subcommand};
use curl::easy::Easy;
use tokio_listener::ListenerAddress;
use iocaine::{app::Iocaine, config::Config, sex_dungeon};
#[derive(Debug, Parser, Default)]
#[command(version, about)]
pub struct Args {
#[arg(short = 'c', long, default_value_t = Self::xdg_config_file())]
pub config_file: String,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Debug, Subcommand)]
enum Commands {
Start,
Reload,
Test,
}
impl Args {
fn xdg_config_file() -> String {
"config.toml".to_owned()
}
}
#[tokio::main]
async fn main() -> Result<()> {
#[cfg(all(feature = "tokio-console", not(tokio_unstable)))]
compile_error!(
"`tokio-console` requires manually enabling the `--cfg tokio_unstable` rust flag during compilation!"
);
#[cfg(all(feature = "tokio-console", tokio_unstable))]
console_subscriber::init();
#[cfg(not(all(feature = "tokio-console", tokio_unstable)))]
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn")),
)
.with_writer(std::io::stderr)
.init();
let args = Args::parse();
let command = args.command.unwrap_or(Commands::Start);
tracing::debug!(config_file = &args.config_file, "loading configuration");
let config = Config::load(&args.config_file).unwrap_or_else(|err| panic!("{}", err));
match command {
Commands::Start => {
tracing::info!("starting iocaine");
let app = Iocaine::new(config)?;
app.run().await?;
}
Commands::Reload => {
reload(&config, &args.config_file)?;
}
Commands::Test => {
run_tests(&config)?;
}
}
Ok(())
}
fn reload(config: &Config, config_file: &str) -> Result<()> {
if config.server.control.is_none() {
return Ok(());
}
let socket_path = config.server.control.clone().unwrap().bind;
let mut client = Easy::new();
client.url("http://iocaine/config/load")?;
match socket_path {
ListenerAddress::Tcp(addr) => {
client.url(&format!("http://{addr}/config/load"))?;
}
ListenerAddress::Path(path) => {
client.unix_socket_path(Some(path))?;
}
ListenerAddress::Abstract(s) => {
client.abstract_unix_socket(s.as_bytes())?;
}
_ => {
return Err(anyhow::anyhow!("Unsupported control socket type"));
}
}
client.post(true).unwrap();
client.post_fields_copy(format!(r#"{{"path": "{config_file}"}}"#).as_bytes())?;
let mut list = curl::easy::List::new();
list.append("content-type: application/json")?;
client.http_headers(list)?;
client.perform()?;
match client.response_code()? {
202 => Ok(()),
409 => Err(anyhow::anyhow!("New configuration is incompatible")),
422 => Err(anyhow::anyhow!(
"New configuration could not be loaded or parsed"
)),
v => Err(anyhow::anyhow!("Unexpected response status: {v}")),
}
}
fn run_tests(config: &Config) -> Result<()> {
let Some(path) = &config.server.request_handler.path else {
return Ok(());
};
let mut npc = sex_dungeon::create(
config.server.request_handler.language,
&config.server.request_handler.options,
path,
None,
)?;
npc.run_tests()
}