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}