1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::sync::Arc;
use std::time::{Duration, SystemTime};

use arc_swap::ArcSwap;
use yaml_rust::{Yaml, YamlLoader};

use crate::infograph::docs::Doc;
use crate::infograph::yaml::hash_to_doc;
use crate::platform::Platform;

pub struct Config {
    filename: String,
    tx: tokio::sync::broadcast::Sender<()>,
    config: ArcSwap<(Doc, Option<SystemTime>)>,
}

pub type ChangeNotifier = tokio::sync::broadcast::Receiver<()>;

pub struct Handle {
    config: Arc<(Doc, Option<SystemTime>)>,
}

impl Config {
    pub fn new(file: &str) -> Self {
        let (tx, _) = tokio::sync::broadcast::channel(1);
        Config {
            filename: file.to_owned(),
            config: ArcSwap::new(Arc::new((Doc::empty(), None))),
            tx,
        }
    }

    pub fn notifier(&self) -> ChangeNotifier {
        self.tx.subscribe()
    }

    pub fn current(&self) -> Handle {
        Handle {
            config: self.config.load_full().clone(),
        }
    }

    fn last_modified(&self) -> Option<SystemTime> {
        std::fs::metadata(&self.filename)
            .ok()
            .and_then(|meta| meta.modified().ok())
    }

    pub fn load(&self) -> anyhow::Result<()> {
        log::info!("Loading config file {}...", &self.filename);

        let config_data = match std::fs::read_to_string(&self.filename) {
            Ok(data) => data,
            Err(error) => {
                return Err(anyhow::anyhow!(
                    "Cannot load config file {}: {}",
                    &self.filename,
                    error
                ));
            }
        };

        let last_modified = std::fs::metadata(&self.filename)
            .ok()
            .and_then(|metadata| metadata.modified().ok());

        self.load_from_string(config_data.as_str(), last_modified)
    }

    pub fn load_from_string(
        &self,
        data: &str,
        last_modified: Option<SystemTime>,
    ) -> anyhow::Result<()> {
        let docs = match YamlLoader::load_from_str(data) {
            Ok(docs) => docs,
            Err(error) => {
                return Err(anyhow::anyhow!(
                    "Cannot parse config file {}: {}",
                    &self.filename,
                    error
                ));
            }
        };

        let doc = if let Some(Yaml::Hash(map)) = docs.get(0) {
            hash_to_doc(map)?
        } else {
            Doc::empty()
        };
        self.config.store(Arc::new((doc, last_modified)));

        Ok(())
    }
}

impl Handle {
    pub fn config(&self) -> &Doc {
        &self.config.0
    }
}

pub fn install(platform: Arc<Platform>) {
    let config = Arc::new(Config::new("settings.yml"));
    platform.register::<Config>(config.clone());

    if let Err(error) = config.load() {
        log::error!("{}", error);
    }

    tokio::spawn(async move {
        while platform.is_running() {
            tokio::time::delay_for(Duration::from_secs(2)).await;
            let last_modified = config.last_modified();
            let last_loaded = config.config.load().1;
            if last_modified.is_some() && (last_loaded.is_none() || last_modified > last_loaded) {
                match config.load() {
                    Ok(_) => {
                        log::info!("System configuration was re-loaded.");
                        if let Err(_) = config.tx.clone().send(()) {
                            log::error!("Failed to broadcast system config change...");
                        }
                    }
                    Err(error) => log::error!("{}", error),
                }
            }
        }
    });
}