jupiter_rs/
config.rs

1use std::sync::Arc;
2use std::time::{Duration, SystemTime};
3
4use arc_swap::ArcSwap;
5use yaml_rust::{Yaml, YamlLoader};
6
7use crate::infograph::docs::Doc;
8use crate::infograph::yaml::hash_to_doc;
9use crate::platform::Platform;
10
11pub struct Config {
12    filename: String,
13    tx: tokio::sync::broadcast::Sender<()>,
14    config: ArcSwap<(Doc, Option<SystemTime>)>,
15}
16
17pub type ChangeNotifier = tokio::sync::broadcast::Receiver<()>;
18
19pub struct Handle {
20    config: Arc<(Doc, Option<SystemTime>)>,
21}
22
23impl Config {
24    pub fn new(file: &str) -> Self {
25        let (tx, _) = tokio::sync::broadcast::channel(1);
26        Config {
27            filename: file.to_owned(),
28            config: ArcSwap::new(Arc::new((Doc::empty(), None))),
29            tx,
30        }
31    }
32
33    pub fn notifier(&self) -> ChangeNotifier {
34        self.tx.subscribe()
35    }
36
37    pub fn current(&self) -> Handle {
38        Handle {
39            config: self.config.load_full().clone(),
40        }
41    }
42
43    fn last_modified(&self) -> Option<SystemTime> {
44        std::fs::metadata(&self.filename)
45            .ok()
46            .and_then(|meta| meta.modified().ok())
47    }
48
49    pub fn load(&self) -> anyhow::Result<()> {
50        log::info!("Loading config file {}...", &self.filename);
51
52        let config_data = match std::fs::read_to_string(&self.filename) {
53            Ok(data) => data,
54            Err(error) => {
55                return Err(anyhow::anyhow!(
56                    "Cannot load config file {}: {}",
57                    &self.filename,
58                    error
59                ));
60            }
61        };
62
63        let last_modified = std::fs::metadata(&self.filename)
64            .ok()
65            .and_then(|metadata| metadata.modified().ok());
66
67        self.load_from_string(config_data.as_str(), last_modified)
68    }
69
70    pub fn load_from_string(
71        &self,
72        data: &str,
73        last_modified: Option<SystemTime>,
74    ) -> anyhow::Result<()> {
75        let docs = match YamlLoader::load_from_str(data) {
76            Ok(docs) => docs,
77            Err(error) => {
78                return Err(anyhow::anyhow!(
79                    "Cannot parse config file {}: {}",
80                    &self.filename,
81                    error
82                ));
83            }
84        };
85
86        let doc = if let Some(Yaml::Hash(map)) = docs.get(0) {
87            hash_to_doc(map)?
88        } else {
89            Doc::empty()
90        };
91        self.config.store(Arc::new((doc, last_modified)));
92
93        Ok(())
94    }
95}
96
97impl Handle {
98    pub fn config(&self) -> &Doc {
99        &self.config.0
100    }
101}
102
103pub fn install(platform: Arc<Platform>) {
104    let config = Arc::new(Config::new("settings.yml"));
105    platform.register::<Config>(config.clone());
106
107    if let Err(error) = config.load() {
108        log::error!("{}", error);
109    }
110
111    tokio::spawn(async move {
112        while platform.is_running() {
113            tokio::time::delay_for(Duration::from_secs(2)).await;
114            let last_modified = config.last_modified();
115            let last_loaded = config.config.load().1;
116            if last_modified.is_some() && (last_loaded.is_none() || last_modified > last_loaded) {
117                match config.load() {
118                    Ok(_) => {
119                        log::info!("System configuration was re-loaded.");
120                        if let Err(_) = config.tx.clone().send(()) {
121                            log::error!("Failed to broadcast system config change...");
122                        }
123                    }
124                    Err(error) => log::error!("{}", error),
125                }
126            }
127        }
128    });
129}