rnotifylib/
config.rs

1use std::path::PathBuf;
2use serde::{Deserialize, Serialize};
3use std::fs::File;
4use std::io::{Read, Write};
5use crate::destination::routed_destination::RoutedDestination;
6use crate::destination::kinds::file::FileDestination;
7use crate::destination::{MessageDestination, SerializableDestination};
8use crate::message_router::RoutingInfo;
9
10const CONFIG_FILE_NAME: &str = "rnotify.toml";
11
12#[derive(Serialize, Deserialize, Debug)]
13#[serde(deny_unknown_fields)]
14pub struct Config {
15    destinations: Vec<SerializableRoutedDestination>,
16}
17
18#[derive(Debug, Serialize, Deserialize)]
19pub struct SerializableRoutedDestination {
20    id: String,
21    // Whether errors with sending notifications will be reported to this destination.
22    #[serde(flatten)]
23    destination: Box<dyn SerializableDestination>,
24    #[serde(flatten)]
25    routing_info: RoutingInfo,
26}
27
28impl SerializableRoutedDestination {
29
30    pub fn new(id: String, destination: Box<dyn SerializableDestination>, routing_info: RoutingInfo) -> Self {
31        Self {
32            id,
33            destination,
34            routing_info,
35        }
36    }
37
38    pub fn create<D: SerializableDestination + 'static>(id: String, destination: D, routing_info: RoutingInfo) -> Self {
39        Self {
40            id,
41            destination: Box::new(destination),
42            routing_info
43        }
44    }
45}
46
47impl RoutedDestination for SerializableRoutedDestination {
48    fn get_id(&self) -> &str {
49        &self.id
50    }
51
52    fn get_destination(&self) -> &dyn MessageDestination {
53        self.destination.as_message_destination()
54    }
55
56    fn get_routing_info(&self) -> &RoutingInfo {
57        &self.routing_info
58    }
59}
60
61impl Config {
62    pub fn get_destinations(&self) -> &Vec<SerializableRoutedDestination> {
63        &self.destinations
64    }
65
66    pub fn take_destinations(self) -> Vec<SerializableRoutedDestination> {
67        self.destinations
68    }
69
70    fn try_default() -> Result<Self, String> {
71        let log_path = dirs::state_dir()
72            .or_else(|| dirs::home_dir());
73
74        if log_path.is_none() {
75            return Err("Failed to get state directory - if you're on linux, is $HOME set?".to_owned());
76        }
77        let mut log_path = log_path.unwrap();
78        log_path.push("rnotify.log");
79
80        Ok(Self {
81            destinations: vec![
82                SerializableRoutedDestination::create("file_log".to_owned(), FileDestination::new(log_path), RoutingInfo::root()),
83            ]
84        })
85    }
86}
87
88pub fn read_config_file(mut file: File) -> Config {
89    let mut s = String::new();
90    file.read_to_string(&mut s).expect("Failed to read config file.");
91    match toml::from_str(&s) {
92        Ok(c) => c,
93        Err(err) => panic!("Error parsing config file:{}", err),
94    }
95}
96
97pub fn fetch_config_file(verbose: bool, config_file_path_override: &Option<PathBuf>) -> Result<File, String> {
98    if config_file_path_override.is_some() {
99        return File::options().read(true).open(config_file_path_override.as_ref().unwrap())
100            .map_err(|e| format!("Failed to open config file {:?} provided by argument for reading: {}", config_file_path_override, e));
101    }
102
103    let config_dir = dirs::config_dir();
104    if config_dir.is_none() {
105        return Err("Failed to find config directory - if you're on linux, is $HOME set?".to_owned());
106    }
107    let mut path_buf = config_dir.unwrap();
108    path_buf.push(CONFIG_FILE_NAME);
109
110    if verbose {
111        println!("Using config file path: {}", &path_buf.display());
112    }
113
114    if !path_buf.exists() {
115        println!("Config file doesn't exist, creating it ({})", &path_buf.display());
116        let mut file = File::options()
117            .create_new(true)
118            .write(true)
119            .open(&path_buf)
120            .expect("Failed to create new config file to write default config.");
121
122        let default_config = Config::try_default().expect("Failed to create default config");
123        let string = toml::to_string(&default_config)
124            .expect("Failed to serialize default config.");
125        file.write_all(string.as_bytes()).expect("Failed to write default config file to config file.");
126        println!("Created default config file");
127    }
128
129    File::options()
130        .read(true)
131        .open(&path_buf)
132        .map_err(|e| format!("Failed to open config file for reading: {}", e))
133}
134
135#[cfg(test)]
136mod tests {
137    use std::fs;
138    use crate::destination::kinds::discord::DiscordDestination;
139    use crate::destination::routed_destination::{MessageRoutingBehaviour, RoutedDestinationBase};
140    use super::*;
141
142    #[test]
143    fn test_mixed() {
144        let s = fs::read_to_string("test/mixed.toml").expect("Failed to read file");
145        let config: Config = toml::from_str(&s).expect("Failed to deserialize.");
146
147        let file_dest = FileDestination::new(PathBuf::from("/var/log/rnotify.log"));
148        let dsc_dest = DiscordDestination::new("https://discord.com/api/webhooks/11111111111111/2aaaaaaaaaaaaaaaaa".to_owned());
149
150
151        let log_file = RoutedDestinationBase::create("log_file".to_owned(), file_dest, RoutingInfo::root());
152        let dsc = RoutedDestinationBase::create("discord_destination".to_owned(), dsc_dest, RoutingInfo::of(MessageRoutingBehaviour::Additive));
153
154        assert_eq!(config.destinations[0].get_id(), log_file.get_id());
155        assert_eq!(config.destinations[0].get_routing_info(), log_file.get_routing_info());
156
157        assert_eq!(config.destinations[1].get_id(), dsc.get_id());
158        assert_eq!(config.destinations[1].get_routing_info(), dsc.get_routing_info());
159    }
160}