futuresdr/runtime/
config.rs

1//! Configuration Management
2#[cfg(not(target_arch = "wasm32"))]
3use config::File;
4#[cfg(not(target_arch = "wasm32"))]
5use config::Source;
6use config::Value;
7use once_cell::sync::Lazy;
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::str::FromStr;
11use std::sync::Mutex;
12use std::sync::MutexGuard;
13use tracing::level_filters::LevelFilter;
14
15/// Get global configuration
16pub fn config() -> Config {
17    get_config().clone()
18}
19
20// helper to deal with poisoned Mutex
21fn get_config() -> MutexGuard<'static, Config> {
22    CONFIG.lock().unwrap_or_else(|poison| {
23        warn!("config poisoned, restoring initial config");
24        let mut c = poison.into_inner();
25        *c = init_config();
26        CONFIG.clear_poison();
27        c
28    })
29}
30
31/// Set config value
32pub fn set<V: Into<config::Value>>(name: impl Into<String>, value: V) {
33    get_config().set_value(name, value);
34}
35
36/// Get value from config
37pub fn get_value(name: &str) -> Option<Value> {
38    get_config().misc.get(name).cloned()
39}
40
41/// Try to parse value from config string
42pub fn get<T: FromStr>(name: &str) -> Option<T> {
43    get_config()
44        .misc
45        .get(name)
46        .and_then(|v| v.clone().into_string().ok())
47        .and_then(|v| v.parse::<T>().ok())
48}
49
50#[cfg(not(target_arch = "wasm32"))]
51fn init_config() -> Config {
52    let mut settings = ::config::Config::builder();
53
54    // user config
55    if let Some(mut path) = dirs::config_dir() {
56        path.push("futuresdr");
57        path.push("config.toml");
58
59        settings = settings.add_source(File::from(path.clone()).required(false));
60    }
61
62    // project config
63    settings =
64        settings.add_source(File::new("config.toml", config::FileFormat::Toml).required(false));
65
66    // env config
67    settings = settings.add_source(config::Environment::with_prefix("futuresdr"));
68
69    // start from default config
70    let mut c = Config::default();
71
72    match settings.build() {
73        Ok(settings) => match settings.collect() {
74            Ok(config) => {
75                for (k, v) in config.iter() {
76                    match k.as_str() {
77                        "queue_size" => {
78                            c.queue_size = config_parse::<usize>(v);
79                        }
80                        "buffer_size" => {
81                            c.buffer_size = config_parse::<usize>(v);
82                        }
83                        "stack_size" => {
84                            c.stack_size = config_parse::<usize>(v);
85                        }
86                        "slab_reserved" => {
87                            c.slab_reserved = config_parse::<usize>(v);
88                        }
89                        "log_level" => {
90                            c.log_level = config_parse::<LevelFilter>(v);
91                        }
92                        "ctrlport_enable" => {
93                            c.ctrlport_enable = config_parse::<bool>(v);
94                        }
95                        "ctrlport_bind" => {
96                            c.ctrlport_bind = v.to_string();
97                        }
98                        "frontend_path" => {
99                            c.frontend_path = Some(config_parse::<PathBuf>(v));
100                        }
101                        _ => {
102                            c.misc.insert(k.clone(), v.clone());
103                        }
104                    }
105                }
106            }
107            Err(e) => warn!("error parsing config {e:?}"),
108        },
109        Err(e) => warn!("error reading config {e:?}"),
110    }
111    c
112}
113
114#[cfg(target_arch = "wasm32")]
115fn init_config() -> Config {
116    Config::default()
117}
118
119static CONFIG: Lazy<Mutex<Config>> = Lazy::new(|| Mutex::new(init_config()));
120
121/// Configuration
122#[derive(Debug, Clone)]
123pub struct Config {
124    /// Queue size of inboxes
125    pub queue_size: usize,
126    /// Stream buffer size in bytes
127    pub buffer_size: usize,
128    /// Thread stack size
129    pub stack_size: usize,
130    /// Slab reserved items
131    pub slab_reserved: usize,
132    /// Log level
133    pub log_level: LevelFilter,
134    /// Enable control port
135    pub ctrlport_enable: bool,
136    /// Control port socket address
137    pub ctrlport_bind: String,
138    /// Frontend path for Webserver
139    pub frontend_path: Option<PathBuf>,
140    misc: HashMap<String, Value>,
141}
142
143impl Config {
144    fn set_value<V: Into<config::Value>>(&mut self, name: impl Into<String>, value: V) {
145        let name = name.into();
146        let value = value.into();
147
148        match name.as_str() {
149            "queue_size" => {
150                self.queue_size = config_parse::<usize>(&value);
151            }
152            "buffer_size" => {
153                self.buffer_size = config_parse::<usize>(&value);
154            }
155            "stack_size" => {
156                self.stack_size = config_parse::<usize>(&value);
157            }
158            "slab_reserved" => {
159                self.slab_reserved = config_parse::<usize>(&value);
160            }
161            "log_level" => {
162                self.log_level = config_parse::<LevelFilter>(&value);
163            }
164            "ctrlport_enable" => {
165                self.ctrlport_enable = config_parse::<bool>(&value);
166            }
167            "ctrlport_bind" => {
168                self.ctrlport_bind = value.to_string();
169            }
170            "frontend_path" => {
171                self.frontend_path = Some(config_parse::<PathBuf>(&value));
172            }
173            _ => {
174                self.misc.insert(name, value);
175            }
176        }
177    }
178}
179
180impl Default for Config {
181    #[cfg(debug_assertions)]
182    fn default() -> Self {
183        Config {
184            queue_size: 8192,
185            buffer_size: 32768,
186            stack_size: 16 * 1024 * 1024,
187            slab_reserved: 0,
188            log_level: LevelFilter::DEBUG,
189            ctrlport_enable: true,
190            ctrlport_bind: "127.0.0.1:1337".to_string(),
191            frontend_path: None,
192            misc: HashMap::new(),
193        }
194    }
195
196    #[cfg(not(debug_assertions))]
197    fn default() -> Self {
198        Config {
199            queue_size: 8192,
200            buffer_size: 32768,
201            stack_size: 16 * 1024 * 1024,
202            slab_reserved: 0,
203            log_level: LevelFilter::INFO,
204            ctrlport_enable: true,
205            ctrlport_bind: "127.0.0.1:1337".to_string(),
206            frontend_path: None,
207            misc: HashMap::new(),
208        }
209    }
210}
211
212// #[cfg(not(target_arch = "wasm32"))]
213fn config_parse<T: FromStr>(v: &Value) -> T {
214    if let Ok(v) = v.clone().into_string() {
215        if let Ok(v) = v.parse::<T>() {
216            return v;
217        }
218    }
219
220    println!("invalid config value {v:?}");
221    panic!();
222}