use std::{
ffi::{CStr, c_char},
ptr,
sync::{LazyLock, Mutex},
};
use fswtch::{FALSE, Module, SUCCESS, Status, Stream, sys};
static CONFIG: LazyLock<Mutex<Config>> = LazyLock::new(|| Mutex::new(Config::default()));
fswtch::module_exports! {
module = mod_config_xml,
load = switch_module_load,
}
#[derive(Debug, Clone)]
struct Config {
enabled: bool,
greeting: String,
max_sessions: u32,
}
impl Default for Config {
fn default() -> Self {
Self {
enabled: true,
greeting: "hello from XML config".to_owned(),
max_sessions: 8,
}
}
}
unsafe extern "C" fn show_api(
_cmd: *const c_char,
_session: *mut sys::switch_core_session_t,
stream: *mut sys::switch_stream_handle_t,
) -> Status {
fswtch::log_example("mod_config_xml", "rust_config_xml_show invoked");
let config = CONFIG
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
write_response(
stream,
&format!(
"enabled={} greeting={} max_sessions={}\n",
config.enabled, config.greeting, config.max_sessions
),
)
}
unsafe extern "C" fn reload_api(
_cmd: *const c_char,
_session: *mut sys::switch_core_session_t,
stream: *mut sys::switch_stream_handle_t,
) -> Status {
fswtch::log_example("mod_config_xml", "rust_config_xml_reload invoked");
match load_config() {
Ok(config) => {
*CONFIG
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = config;
write_response(stream, "config reloaded\n")
}
Err(error) => write_response(stream, &format!("config reload failed: {error}\n")),
}
}
unsafe extern "C" fn switch_module_load(
module_interface: *mut *mut sys::switch_loadable_module_interface_t,
pool: *mut sys::switch_memory_pool_t,
) -> Status {
fswtch::log_example("mod_config_xml", "loading module");
if let Ok(config) = load_config() {
*CONFIG
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = config;
}
let module = match unsafe { Module::create(module_interface, pool, c"mod_config_xml") } {
Ok(module) => module,
Err(error) => return error.0,
};
for result in [
unsafe {
module.add_api(
c"rust_config_xml_show",
c"prints settings loaded from fswtch_examples.conf",
c"rust_config_xml_show",
show_api,
)
},
unsafe {
module.add_api(
c"rust_config_xml_reload",
c"reloads settings from fswtch_examples.conf",
c"rust_config_xml_reload",
reload_api,
)
},
] {
if let Err(error) = result {
return error.0;
}
}
SUCCESS
}
fn load_config() -> Result<Config, &'static str> {
fswtch::log_example("mod_config_xml", "loading fswtch_examples.conf");
let mut config = Config::default();
let mut settings = ptr::null_mut();
let root = unsafe {
sys::switch_xml_open_cfg(
c"fswtch_examples.conf".as_ptr(),
&mut settings,
ptr::null_mut(),
)
};
if root.is_null() {
return Err("fswtch_examples.conf not found");
}
if !settings.is_null() {
let settings_node = unsafe { sys::switch_xml_child(settings, c"settings".as_ptr()) };
if !settings_node.is_null() {
parse_settings(settings_node, &mut config);
}
}
unsafe {
sys::switch_xml_free(root);
}
Ok(config)
}
fn parse_settings(settings: sys::switch_xml_t, config: &mut Config) {
let mut param = unsafe { sys::switch_xml_child(settings, c"param".as_ptr()) };
while !param.is_null() {
let Some(name) = xml_attr(param, c"name") else {
param = unsafe { (*param).next };
continue;
};
let Some(value) = xml_attr(param, c"value") else {
param = unsafe { (*param).next };
continue;
};
match name.as_str() {
"enabled" => config.enabled = matches!(value.as_str(), "true" | "yes" | "1"),
"greeting" => config.greeting = value,
"max-sessions" => {
if let Ok(parsed) = value.parse() {
config.max_sessions = parsed;
}
}
_ => {}
}
param = unsafe { (*param).next };
}
}
fn xml_attr(node: sys::switch_xml_t, name: &'static CStr) -> Option<String> {
let value = unsafe { sys::switch_xml_attr(node, name.as_ptr()) };
if value.is_null() {
return None;
}
unsafe { CStr::from_ptr(value) }
.to_str()
.ok()
.map(ToOwned::to_owned)
}
fn write_response(stream: *mut sys::switch_stream_handle_t, text: &str) -> Status {
let Some(mut stream) = (unsafe { Stream::from_raw(stream) }) else {
return FALSE;
};
match stream.write_str(text) {
Ok(()) => SUCCESS,
Err(error) => error.0,
}
}